From 05e06668cb5334d0e4868612b3eac46bd4b72cc8 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Mon, 16 Jun 2025 10:54:04 +0200 Subject: [PATCH 01/26] Added base utilities and implementation for sender schedule (untested) --- apps/socketoptions.hpp | 3 +- srtcore/api.cpp | 337 +++++++++++++++++++------------------ srtcore/api.h | 48 ------ srtcore/buffer_snd.cpp | 75 +++++++-- srtcore/buffer_snd.h | 53 +++--- srtcore/common.cpp | 2 + srtcore/common.h | 44 +++++ srtcore/core.cpp | 318 ++++++++++++++++++++++++++++++++-- srtcore/core.h | 225 ++++++++++++++++++++++++- srtcore/filelist.maf | 1 + srtcore/group.cpp | 6 +- srtcore/packetfilter.cpp | 149 ++++++++++++---- srtcore/packetfilter.h | 30 +++- srtcore/packetfilter_api.h | 2 +- srtcore/queue.cpp | 87 +++++++++- srtcore/queue.h | 8 + srtcore/socketconfig.cpp | 19 +++ srtcore/socketconfig.h | 3 + srtcore/srt.h | 1 + srtcore/utilities.h | 258 ++++++++++++++++++++++++++++ 20 files changed, 1352 insertions(+), 317 deletions(-) diff --git a/apps/socketoptions.hpp b/apps/socketoptions.hpp index 1ffb2febb..a802a0096 100644 --- a/apps/socketoptions.hpp +++ b/apps/socketoptions.hpp @@ -254,7 +254,8 @@ const SocketOption srt_options [] { { "bindtodevice", 0, SRTO_BINDTODEVICE, SocketOption::PRE, SocketOption::STRING, nullptr}, { "retransmitalgo", 0, SRTO_RETRANSMITALGO, SocketOption::PRE, SocketOption::INT, nullptr }, { "cryptomode", 0, SRTO_CRYPTOMODE, SocketOption::PRE, SocketOption::INT, nullptr }, - { "maxrexmitbw", 0, SRTO_MAXREXMITBW, SocketOption::POST, SocketOption::INT64, nullptr } + { "maxrexmitbw", 0, SRTO_MAXREXMITBW, SocketOption::POST, SocketOption::INT64, nullptr }, + { "sendmode", 0, SRTO_SENDMODE, SocketOption::PRE, SocketOption::INT, nullptr } }; } diff --git a/srtcore/api.cpp b/srtcore/api.cpp index e7d3f1467..113fa254f 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -81,7 +81,11 @@ using namespace std; using namespace srt_logging; using namespace srt::sync; -void srt::CUDTSocket::construct() + +namespace srt +{ + +void CUDTSocket::construct() { #if ENABLE_BONDING m_GroupOf = NULL; @@ -92,14 +96,14 @@ void srt::CUDTSocket::construct() setupMutex(m_ControlLock, "Control"); } -srt::CUDTSocket::~CUDTSocket() +CUDTSocket::~CUDTSocket() { releaseMutex(m_AcceptLock); releaseCond(m_AcceptCond); releaseMutex(m_ControlLock); } -SRT_SOCKSTATUS srt::CUDTSocket::getStatus() +SRT_SOCKSTATUS CUDTSocket::getStatus() { // TTL in CRendezvousQueue::updateConnStatus() will set m_bConnecting to false. // Although m_Status is still SRTS_CONNECTING, the connection is in fact to be closed due to TTL expiry. @@ -117,7 +121,7 @@ SRT_SOCKSTATUS srt::CUDTSocket::getStatus() } // [[using locked(m_GlobControlLock)]] -void srt::CUDTSocket::breakSocket_LOCKED(int reason) +void CUDTSocket::breakSocket_LOCKED(int reason) { // This function is intended to be called from GC, // under a lock of m_GlobControlLock. @@ -128,7 +132,7 @@ void srt::CUDTSocket::breakSocket_LOCKED(int reason) setClosed(); } -void srt::CUDTSocket::setClosed() +void CUDTSocket::setClosed() { m_Status = SRTS_CLOSED; @@ -139,14 +143,14 @@ void srt::CUDTSocket::setClosed() m_tsClosureTimeStamp = steady_clock::now(); } -void srt::CUDTSocket::setBrokenClosed() +void CUDTSocket::setBrokenClosed() { m_UDT.m_iBrokenCounter = 60; m_UDT.m_bBroken = true; setClosed(); } -bool srt::CUDTSocket::readReady() const +bool CUDTSocket::readReady() const { if (m_UDT.m_bConnected && m_UDT.isRcvBufferReady()) return true; @@ -157,17 +161,17 @@ bool srt::CUDTSocket::readReady() const return broken(); } -bool srt::CUDTSocket::writeReady() const +bool CUDTSocket::writeReady() const { return (m_UDT.m_bConnected && (m_UDT.m_pSndBuffer->getCurrBufSize() < m_UDT.m_config.iSndBufSize)) || broken(); } -bool srt::CUDTSocket::broken() const +bool CUDTSocket::broken() const { return m_UDT.m_bBroken || !m_UDT.m_bConnected; } -srt::CMultiplexer* srt::CUDTSocket::notListening() +CMultiplexer* CUDTSocket::notListening() { CMultiplexer* m = core().notListening(); // This dissociates the socket from the muxer, so reset the muxer id @@ -178,7 +182,7 @@ srt::CMultiplexer* srt::CUDTSocket::notListening() //////////////////////////////////////////////////////////////////////////////// -srt::CUDTUnited::CUDTUnited() +CUDTUnited::CUDTUnited() : m_Sockets() , m_GlobControlLock() , m_IDLock() @@ -216,7 +220,7 @@ srt::CUDTUnited::CUDTUnited() HLOGC(inlog.Debug, log << "SRT Clock Type: " << SRT_SYNC_CLOCK_STR); } -srt::CUDTUnited::~CUDTUnited() +CUDTUnited::~CUDTUnited() { // Call it if it wasn't called already. // This will happen at the end of main() of the application, @@ -245,7 +249,7 @@ srt::CUDTUnited::~CUDTUnited() #endif } -string srt::CUDTUnited::CONID(SRTSOCKET sock) +string CUDTUnited::CONID(SRTSOCKET sock) { if (int32_t(sock) <= 0) // embraces SRT_INVALID_SOCK, SRT_SOCKID_CONNREQ and illegal negative domain return ""; @@ -255,7 +259,7 @@ string srt::CUDTUnited::CONID(SRTSOCKET sock) return os.str(); } -bool srt::CUDTUnited::startGarbageCollector() +bool CUDTUnited::startGarbageCollector() { ScopedLock guard(m_GCStartLock); @@ -267,7 +271,7 @@ bool srt::CUDTUnited::startGarbageCollector() return m_bGCStatus; } -void srt::CUDTUnited::stopGarbageCollector() +void CUDTUnited::stopGarbageCollector() { ScopedLock guard(m_GCStartLock); @@ -283,7 +287,7 @@ void srt::CUDTUnited::stopGarbageCollector() } } -void srt::CUDTUnited::closeAllSockets() +void CUDTUnited::closeAllSockets() { // remove all sockets and multiplexers HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquring control lock..."); @@ -352,14 +356,14 @@ void srt::CUDTUnited::closeAllSockets() break; HLOGC(inlog.Debug, log << "GC: checkBrokenSockets didn't wipe all sockets, repeating after 1s sleep"); - srt::sync::this_thread::sleep_for(milliseconds_from(1)); + sync::this_thread::sleep_for(milliseconds_from(1)); } } -SRTRUNSTATUS srt::CUDTUnited::startup() +SRTRUNSTATUS CUDTUnited::startup() { ScopedLock gcinit(m_InitLock); m_iInstanceCount++; @@ -369,7 +373,7 @@ SRTRUNSTATUS srt::CUDTUnited::startup() return startGarbageCollector() ? SRT_RUN_OK : SRT_RUN_ERROR; } -SRTSTATUS srt::CUDTUnited::cleanup() +SRTSTATUS CUDTUnited::cleanup() { // IMPORTANT!!! // In this function there must be NO LOGGING AT ALL. This function may @@ -389,7 +393,7 @@ SRTSTATUS srt::CUDTUnited::cleanup() return SRT_STATUS_OK; } -SRTSOCKET srt::CUDTUnited::generateSocketID(bool for_group) +SRTSOCKET CUDTUnited::generateSocketID(bool for_group) { ScopedLock guard(m_IDLock); @@ -492,7 +496,7 @@ SRTSOCKET srt::CUDTUnited::generateSocketID(bool for_group) return SRTSOCKET(sockval); } -SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) +SRTSOCKET CUDTUnited::newSocket(CUDTSocket** pps) { // XXX consider using some replacement of std::unique_ptr // so that exceptions will clean up the object without the @@ -546,7 +550,7 @@ SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) } // [[using locked(m_GlobControlLock)]] -void srt::CUDTUnited::swipeSocket_LOCKED(SRTSOCKET id, CUDTSocket* s, CUDTUnited::SwipeSocketTerm lateremove) +void CUDTUnited::swipeSocket_LOCKED(SRTSOCKET id, CUDTSocket* s, CUDTUnited::SwipeSocketTerm lateremove) { m_ClosedSockets[id] = s; if (!lateremove) @@ -555,7 +559,7 @@ void srt::CUDTUnited::swipeSocket_LOCKED(SRTSOCKET id, CUDTSocket* s, CUDTUnited } } -int srt::CUDTUnited::newConnection(const SRTSOCKET listener, +int CUDTUnited::newConnection(const SRTSOCKET listener, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs, @@ -913,7 +917,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listener, return 1; } -SRT_EPOLL_T srt::CUDTSocket::getListenerEvents() +SRT_EPOLL_T CUDTSocket::getListenerEvents() { // You need to check EVERY socket that has been queued // and verify its internals. With independent socket the @@ -941,7 +945,7 @@ SRT_EPOLL_T srt::CUDTSocket::getListenerEvents() } #if ENABLE_BONDING -int srt::CUDTUnited::checkQueuedSocketsEvents(const map& sockets) +int CUDTUnited::checkQueuedSocketsEvents(const map& sockets) { SRT_EPOLL_T flags = 0; @@ -976,12 +980,12 @@ int srt::CUDTUnited::checkQueuedSocketsEvents(const map #endif // static forwarder -SRTSTATUS srt::CUDT::installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) +SRTSTATUS CUDT::installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) { return uglobal().installAcceptHook(lsn, hook, opaq); } -SRTSTATUS srt::CUDTUnited::installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) +SRTSTATUS CUDTUnited::installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) { try { @@ -997,12 +1001,12 @@ SRTSTATUS srt::CUDTUnited::installAcceptHook(const SRTSOCKET lsn, srt_listen_cal return SRT_STATUS_OK; } -SRTSTATUS srt::CUDT::installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) +SRTSTATUS CUDT::installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) { return uglobal().installConnectHook(lsn, hook, opaq); } -SRTSTATUS srt::CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_fn* hook, void* opaq) +SRTSTATUS CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_fn* hook, void* opaq) { try { @@ -1026,7 +1030,7 @@ SRTSTATUS srt::CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_cal return SRT_STATUS_OK; } -SRT_SOCKSTATUS srt::CUDTUnited::getStatus(const SRTSOCKET u) +SRT_SOCKSTATUS CUDTUnited::getStatus(const SRTSOCKET u) { // protects the m_Sockets structure ScopedLock cg(m_GlobControlLock); @@ -1043,7 +1047,7 @@ SRT_SOCKSTATUS srt::CUDTUnited::getStatus(const SRTSOCKET u) return i->second->getStatus(); } -SRTSTATUS srt::CUDTUnited::getCloseReason(const SRTSOCKET u, SRT_CLOSE_INFO& info) +SRTSTATUS CUDTUnited::getCloseReason(const SRTSOCKET u, SRT_CLOSE_INFO& info) { // protects the m_Sockets structure ScopedLock cg(m_GlobControlLock); @@ -1074,7 +1078,7 @@ SRTSTATUS srt::CUDTUnited::getCloseReason(const SRTSOCKET u, SRT_CLOSE_INFO& inf return SRT_STATUS_OK; } -SRTSTATUS srt::CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name) +SRTSTATUS CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name) { ScopedLock cg(s->m_ControlLock); @@ -1095,7 +1099,7 @@ SRTSTATUS srt::CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name) return SRT_STATUS_OK; } -SRTSTATUS srt::CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock) +SRTSTATUS CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock) { ScopedLock cg(s->m_ControlLock); @@ -1117,7 +1121,7 @@ SRTSTATUS srt::CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock) return SRT_STATUS_OK; } -void srt::CUDTUnited::bindSocketToMuxer(CUDTSocket* s, const sockaddr_any& address, SRTSOCKET* psocket) +void CUDTUnited::bindSocketToMuxer(CUDTSocket* s, const sockaddr_any& address, SRTSOCKET* psocket) { if (address.hport() == 0 && s->core().m_config.bRendezvous) throw CUDTException(MJ_NOTSUP, MN_ISRENDUNBOUND, 0); @@ -1132,7 +1136,7 @@ void srt::CUDTUnited::bindSocketToMuxer(CUDTSocket* s, const sockaddr_any& addre s->m_SelfAddr = s->core().channel()->getSockAddr(); } -SRTSTATUS srt::CUDTUnited::listen(const SRTSOCKET u, int backlog) +SRTSTATUS CUDTUnited::listen(const SRTSOCKET u, int backlog) { if (backlog <= 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -1178,7 +1182,7 @@ SRTSTATUS srt::CUDTUnited::listen(const SRTSOCKET u, int backlog) return SRT_STATUS_OK; } -SRTSOCKET srt::CUDTUnited::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut) +SRTSOCKET CUDTUnited::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut) { CEPollDesc* ed = 0; int eid = m_EPoll.create(&ed); @@ -1223,7 +1227,7 @@ SRTSOCKET srt::CUDTUnited::accept_bond(const SRTSOCKET listeners[], int lsize, i return accept(lsn, ((sockaddr*)&dummy), (&outlen)); } -SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int* pw_addrlen) +SRTSOCKET CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int* pw_addrlen) { if (pw_addr && !pw_addrlen) { @@ -1374,7 +1378,7 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int #if ENABLE_BONDING // [[using locked(m_GlobControlLock)]] -void srt::CUDTUnited::removePendingForGroup(const CUDTGroup* g) +void CUDTUnited::removePendingForGroup(const CUDTGroup* g) { // We don't have a list of listener sockets that have ever // reported a pending connection for a group, so the only @@ -1444,7 +1448,7 @@ void srt::CUDTUnited::removePendingForGroup(const CUDTGroup* g) #endif -SRTSOCKET srt::CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int namelen) +SRTSOCKET CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int namelen) { // Here both srcname and tarname must be specified if (!srcname || !tarname || namelen < int(sizeof(sockaddr_in))) @@ -1491,7 +1495,7 @@ SRTSOCKET srt::CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const s return SRT_SOCKID_CONNREQ; } -SRTSOCKET srt::CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) +SRTSOCKET CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) { if (!name || namelen < int(sizeof(sockaddr_in))) { @@ -1529,7 +1533,7 @@ SRTSOCKET srt::CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int } #if ENABLE_BONDING -SRTSOCKET srt::CUDTUnited::singleMemberConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* gd) +SRTSOCKET CUDTUnited::singleMemberConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* gd) { SRTSOCKET gstat = groupConnect(pg, gd, 1); if (gstat == SRT_INVALID_SOCK) @@ -1546,7 +1550,7 @@ SRTSOCKET srt::CUDTUnited::singleMemberConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFI } // [[using assert(pg->m_iBusy > 0)]] -SRTSOCKET srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, int arraysize) +SRTSOCKET CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, int arraysize) { CUDTGroup& g = *pg; SRT_ASSERT(g.m_iBusy > 0); @@ -1679,7 +1683,7 @@ SRTSOCKET srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targ // Do it after setting all stored options, as some of them may // influence some group data. - srt::groups::SocketData data = srt::groups::prepareSocketData(ns); + groups::SocketData data = groups::prepareSocketData(ns); if (targets[tii].token != -1) { // Reuse the token, if specified by the caller @@ -2084,7 +2088,7 @@ SRTSOCKET srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targ } #endif -void srt::CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, int32_t forced_isn) +void CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, int32_t forced_isn) { ScopedLock cg(s->m_ControlLock); // a socket can "connect" only if it is in the following states: @@ -2140,7 +2144,7 @@ void srt::CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, } } -SRTSTATUS srt::CUDTUnited::close(const SRTSOCKET u, int reason) +SRTSTATUS CUDTUnited::close(const SRTSOCKET u, int reason) { #if ENABLE_BONDING if (CUDT::isgroup(u)) @@ -2168,7 +2172,8 @@ SRTSTATUS srt::CUDTUnited::close(const SRTSOCKET u, int reason) }; #endif - SocketKeeper k(*this, u, ERH_THROW); + SocketKeeper k = CUDT::keep(u, ERH_THROW); + IF_HEAVY_LOGGING(ScopedExitLog slog(k.socket)); HLOGC(smlog.Debug, log << "CUDTUnited::close/begin: @" << u << " busy=" << k.socket->isStillBusy()); @@ -2176,16 +2181,16 @@ SRTSTATUS srt::CUDTUnited::close(const SRTSOCKET u, int reason) } #if ENABLE_BONDING -void srt::CUDTUnited::deleteGroup(CUDTGroup* g) +void CUDTUnited::deleteGroup(CUDTGroup* g) { using srt_logging::gmlog; - srt::sync::ScopedLock cg(m_GlobControlLock); + sync::ScopedLock cg(m_GlobControlLock); return deleteGroup_LOCKED(g); } // [[using locked(m_GlobControlLock)]] -void srt::CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) +void CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) { SRT_ASSERT(g->groupEmpty()); @@ -2224,7 +2229,7 @@ void srt::CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) #endif // [[using locked(m_GlobControlLock)]] -void srt::CUDTUnited::recordCloseReason(CUDTSocket* s) +void CUDTUnited::recordCloseReason(CUDTSocket* s) { SRT_CLOSE_INFO info; info.agent = SRT_CLOSE_REASON(s->core().m_AgentCloseReason.load()); @@ -2273,7 +2278,7 @@ void srt::CUDTUnited::recordCloseReason(CUDTSocket* s) } } -bool srt::CUDTSocket::closeInternal(int reason) ATR_NOEXCEPT +bool CUDTSocket::closeInternal(int reason) ATR_NOEXCEPT { bool done = m_UDT.closeEntity(reason); breakNonAcceptedSockets(); // XXX necessary? @@ -2281,7 +2286,7 @@ bool srt::CUDTSocket::closeInternal(int reason) ATR_NOEXCEPT return done; } -void srt::CUDTSocket::breakNonAcceptedSockets() +void CUDTSocket::breakNonAcceptedSockets() { // In case of a listener socket, close also all // accepted sockets. @@ -2304,7 +2309,7 @@ void srt::CUDTSocket::breakNonAcceptedSockets() HLOGC(smlog.Debug, log << "breakNonAcceptedSockets: found " << accepted.size() << " leaky accepted sockets"); for (vector::iterator i = accepted.begin(); i != accepted.end(); ++i) { - CUDTUnited::SocketKeeper sk(m_UDT.uglobal(), *i); + SocketKeeper sk = CUDT::keep(*i, ERH_RETURN); if (sk.socket) { sk.socket->m_UDT.m_bBroken = true; @@ -2320,7 +2325,7 @@ void srt::CUDTSocket::breakNonAcceptedSockets() } } -SRTSTATUS srt::CUDTUnited::close(CUDTSocket* s, int reason) +SRTSTATUS CUDTUnited::close(CUDTSocket* s, int reason) { HLOGC(smlog.Debug, log << s->core().CONID() << "CLOSE. Acquiring control lock"); @@ -2543,7 +2548,7 @@ SRTSTATUS srt::CUDTUnited::close(CUDTSocket* s, int reason) return SRT_STATUS_OK; } -void srt::CUDTUnited::getpeername(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) +void CUDTUnited::getpeername(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) { if (!pw_name || !pw_namelen) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -2567,7 +2572,7 @@ void srt::CUDTUnited::getpeername(const SRTSOCKET u, sockaddr* pw_name, int* pw_ *pw_namelen = len; } -void srt::CUDTUnited::getsockname(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) +void CUDTUnited::getsockname(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) { if (!pw_name || !pw_namelen) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -2591,7 +2596,7 @@ void srt::CUDTUnited::getsockname(const SRTSOCKET u, sockaddr* pw_name, int* pw_ *pw_namelen = len; } -void srt::CUDTUnited::getsockdevname(const SRTSOCKET u, char* pw_name, size_t* pw_namelen) +void CUDTUnited::getsockdevname(const SRTSOCKET u, char* pw_name, size_t* pw_namelen) { if (!pw_name || !pw_namelen) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -2624,7 +2629,7 @@ void srt::CUDTUnited::getsockdevname(const SRTSOCKET u, char* pw_name, size_t* p *pw_namelen = 0; // report empty one } -int srt::CUDTUnited::select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout) +int CUDTUnited::select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout) { const steady_clock::time_point entertime = steady_clock::now(); @@ -2728,7 +2733,7 @@ int srt::CUDTUnited::select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSE return count; } -int srt::CUDTUnited::selectEx(const vector& fds, +int CUDTUnited::selectEx(const vector& fds, vector* readfds, vector* writefds, vector* exceptfds, @@ -2794,17 +2799,17 @@ int srt::CUDTUnited::selectEx(const vector& fds, return count; } -int srt::CUDTUnited::epoll_create() +int CUDTUnited::epoll_create() { return m_EPoll.create(); } -void srt::CUDTUnited::epoll_clear_usocks(int eid) +void CUDTUnited::epoll_clear_usocks(int eid) { return m_EPoll.clear_usocks(eid); } -void srt::CUDTUnited::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) +void CUDTUnited::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) { #if ENABLE_BONDING if (CUDT::isgroup(u)) @@ -2835,24 +2840,24 @@ void srt::CUDTUnited::epoll_add_usock(const int eid, const SRTSOCKET u, const in // NOTE: WILL LOCK (serially): // - CEPoll::m_EPollLock // - CUDT::m_RecvLock -void srt::CUDTUnited::epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events) +void CUDTUnited::epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events) { m_EPoll.update_usock(eid, s->id(), events); s->core().addEPoll(eid); } -void srt::CUDTUnited::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) +void CUDTUnited::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) { return m_EPoll.add_ssock(eid, s, events); } -void srt::CUDTUnited::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) +void CUDTUnited::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) { return m_EPoll.update_ssock(eid, s, events); } template -void srt::CUDTUnited::epoll_remove_entity(const int eid, EntityType* ent) +void CUDTUnited::epoll_remove_entity(const int eid, EntityType* ent) { // XXX Not sure if this is anyhow necessary because setting readiness // to false doesn't actually trigger any action. Further research needed. @@ -2872,19 +2877,19 @@ void srt::CUDTUnited::epoll_remove_entity(const int eid, EntityType* ent) } // Needed internal access! -void srt::CUDTUnited::epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* s) +void CUDTUnited::epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* s) { return epoll_remove_entity(eid, &s->core()); } #if ENABLE_BONDING -void srt::CUDTUnited::epoll_remove_group_INTERNAL(const int eid, CUDTGroup* g) +void CUDTUnited::epoll_remove_group_INTERNAL(const int eid, CUDTGroup* g) { return epoll_remove_entity(eid, g); } #endif -void srt::CUDTUnited::epoll_remove_usock(const int eid, const SRTSOCKET u) +void CUDTUnited::epoll_remove_usock(const int eid, const SRTSOCKET u) { CUDTSocket* s = 0; @@ -2910,27 +2915,27 @@ void srt::CUDTUnited::epoll_remove_usock(const int eid, const SRTSOCKET u) return m_EPoll.update_usock(eid, u, &no_events); } -void srt::CUDTUnited::epoll_remove_ssock(const int eid, const SYSSOCKET s) +void CUDTUnited::epoll_remove_ssock(const int eid, const SYSSOCKET s) { return m_EPoll.remove_ssock(eid, s); } -int srt::CUDTUnited::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) +int CUDTUnited::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { return m_EPoll.uwait(eid, fdsSet, fdsSize, msTimeOut); } -int32_t srt::CUDTUnited::epoll_set(int eid, int32_t flags) +int32_t CUDTUnited::epoll_set(int eid, int32_t flags) { return m_EPoll.setflags(eid, flags); } -void srt::CUDTUnited::epoll_release(const int eid) +void CUDTUnited::epoll_release(const int eid) { return m_EPoll.release(eid); } -srt::CUDTSocket* srt::CUDTUnited::locateSocket(const SRTSOCKET u, ErrorHandling erh) +CUDTSocket* CUDTUnited::locateSocket(const SRTSOCKET u, ErrorHandling erh) { ScopedLock cg(m_GlobControlLock); CUDTSocket* s = locateSocket_LOCKED(u); @@ -2945,7 +2950,7 @@ srt::CUDTSocket* srt::CUDTUnited::locateSocket(const SRTSOCKET u, ErrorHandling } // [[using locked(m_GlobControlLock)]]; -srt::CUDTSocket* srt::CUDTUnited::locateSocket_LOCKED(SRTSOCKET u) +CUDTSocket* CUDTUnited::locateSocket_LOCKED(SRTSOCKET u) { sockets_t::iterator i = m_Sockets.find(u); @@ -2958,7 +2963,7 @@ srt::CUDTSocket* srt::CUDTUnited::locateSocket_LOCKED(SRTSOCKET u) } #if ENABLE_BONDING -srt::CUDTGroup* srt::CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling erh) +CUDTGroup* CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling erh) { ScopedLock cg(m_GlobControlLock); @@ -2975,7 +2980,7 @@ srt::CUDTGroup* srt::CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling e return i->second; } -srt::CUDTGroup* srt::CUDTUnited::acquireSocketsGroup(CUDTSocket* s) +CUDTGroup* CUDTUnited::acquireSocketsGroup(CUDTSocket* s) { ScopedLock cg(m_GlobControlLock); CUDTGroup* g = s->m_GroupOf; @@ -2989,7 +2994,7 @@ srt::CUDTGroup* srt::CUDTUnited::acquireSocketsGroup(CUDTSocket* s) } #endif -srt::CUDTSocket* srt::CUDTUnited::locateAcquireSocket(SRTSOCKET u, ErrorHandling erh) +CUDTSocket* CUDTUnited::locateAcquireSocket(SRTSOCKET u, ErrorHandling erh) { ScopedLock cg(m_GlobControlLock); @@ -3005,7 +3010,7 @@ srt::CUDTSocket* srt::CUDTUnited::locateAcquireSocket(SRTSOCKET u, ErrorHandling return s; } -bool srt::CUDTUnited::acquireSocket(CUDTSocket* s) +bool CUDTUnited::acquireSocket(CUDTSocket* s) { // Note that before using this function you must be certain // that the socket isn't broken already and it still has at least @@ -3028,7 +3033,7 @@ bool srt::CUDTUnited::acquireSocket(CUDTSocket* s) return true; } -srt::CUDTSocket* srt::CUDTUnited::locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn) +CUDTSocket* CUDTUnited::locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn) { ScopedLock cg(m_GlobControlLock); @@ -3052,7 +3057,7 @@ srt::CUDTSocket* srt::CUDTUnited::locatePeer(const sockaddr_any& peer, const SRT return NULL; } -void srt::CUDTUnited::checkBrokenSockets() +void CUDTUnited::checkBrokenSockets() { ScopedLock cg(m_GlobControlLock); @@ -3228,7 +3233,7 @@ void srt::CUDTUnited::checkBrokenSockets() } // [[using locked(m_GlobControlLock)]] -void srt::CUDTUnited::closeLeakyAcceptSockets(CUDTSocket* s) +void CUDTUnited::closeLeakyAcceptSockets(CUDTSocket* s) { ScopedLock cg(s->m_AcceptLock); @@ -3260,7 +3265,7 @@ void srt::CUDTUnited::closeLeakyAcceptSockets(CUDTSocket* s) // [[using locked(m_GlobControlLock)]] -void srt::CUDTUnited::removeSocket(const SRTSOCKET u) +void CUDTUnited::removeSocket(const SRTSOCKET u) { sockets_t::iterator i = m_ClosedSockets.find(u); @@ -3386,7 +3391,7 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u) /// @param mid Muxer ID that identifies the multiplexer in the socket /// @param u Socket ID that was the last multiplexer's user (logging only) // [[using locked(m_GlobControlLock)]] -void srt::CUDTUnited::checkRemoveMux(CMultiplexer& mx) +void CUDTUnited::checkRemoveMux(CMultiplexer& mx) { const int mid = mx.id(); HLOGC(smlog.Debug, log << "removeMux: unrefing muxer " << mid << ", with " << mx.nsockets() << " sockets"); @@ -3408,7 +3413,7 @@ void srt::CUDTUnited::checkRemoveMux(CMultiplexer& mx) } } -void srt::CUDTUnited::checkTemporaryDatabases() +void CUDTUnited::checkTemporaryDatabases() { ScopedLock cg(m_GlobControlLock); @@ -3435,7 +3440,7 @@ void srt::CUDTUnited::checkTemporaryDatabases() // Muxer in this function is added a socket to its lists and pinning // it into the socket, but does not modify any multiplexer's data. -void srt::CUDTUnited::installMuxer(CUDTSocket* pw_s, CMultiplexer* fw_pm) +void CUDTUnited::installMuxer(CUDTSocket* pw_s, CMultiplexer* fw_pm) { pw_s->core().m_pMuxer = fw_pm; pw_s->m_iMuxID = fw_pm->id(); @@ -3444,7 +3449,7 @@ void srt::CUDTUnited::installMuxer(CUDTSocket* pw_s, CMultiplexer* fw_pm) fw_pm->addSocket(pw_s); } -bool srt::CUDTUnited::inet6SettingsCompat(const sockaddr_any& muxaddr, const CSrtMuxerConfig& cfgMuxer, +bool CUDTUnited::inet6SettingsCompat(const sockaddr_any& muxaddr, const CSrtMuxerConfig& cfgMuxer, const sockaddr_any& reqaddr, const CSrtMuxerConfig& cfgSocket) { if (muxaddr.family() != AF_INET6) @@ -3463,7 +3468,7 @@ bool srt::CUDTUnited::inet6SettingsCompat(const sockaddr_any& muxaddr, const CSr return true; } -bool srt::CUDTUnited::channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, const CSrtConfig& cfgSocket) +bool CUDTUnited::channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, const CSrtConfig& cfgSocket) { if (!cfgMuxer.bReuseAddr) { @@ -3478,7 +3483,7 @@ bool srt::CUDTUnited::channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, cons return false; } -void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, const UDPSOCKET* udpsock /*[[nullable]]*/) +void CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, const UDPSOCKET* udpsock /*[[nullable]]*/) { ScopedLock cg(m_GlobControlLock); @@ -3540,7 +3545,7 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons // exists, otherwise the dispatching procedure wouldn't even call this // function. By historical reasons there's also a fallback for a case when the // multiplexer wasn't found by id, the search by port number continues. -bool srt::CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) +bool CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) { ScopedLock cg(m_GlobControlLock); const int port = ls->m_SelfAddr.hport(); @@ -3623,7 +3628,7 @@ bool srt::CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) return false; } -srt::CMultiplexer* srt::CUDTUnited::findSuitableMuxer(CUDTSocket* s, const sockaddr_any& reqaddr) +CMultiplexer* CUDTUnited::findSuitableMuxer(CUDTSocket* s, const sockaddr_any& reqaddr) { // If not, we need to see if there exist already a multiplexer bound // to the same endpoint. @@ -3841,7 +3846,7 @@ srt::CMultiplexer* srt::CUDTUnited::findSuitableMuxer(CUDTSocket* s, const socka return NULL; } -void* srt::CUDTUnited::garbageCollect(void* p) +void* CUDTUnited::garbageCollect(void* p) { CUDTUnited* self = (CUDTUnited*)p; @@ -3867,17 +3872,17 @@ void* srt::CUDTUnited::garbageCollect(void* p) //////////////////////////////////////////////////////////////////////////////// -SRTRUNSTATUS srt::CUDT::startup() +SRTRUNSTATUS CUDT::startup() { return uglobal().startup(); } -SRTSTATUS srt::CUDT::cleanup() +SRTSTATUS CUDT::cleanup() { return uglobal().cleanup(); } -SRTSOCKET srt::CUDT::socket() +SRTSOCKET CUDT::socket() { try { @@ -3901,17 +3906,17 @@ SRTSOCKET srt::CUDT::socket() } } -srt::CUDT::APIError::APIError(const CUDTException& e) +CUDT::APIError::APIError(const CUDTException& e) { SetThreadLocalError(e); } -srt::CUDT::APIError::APIError(CodeMajor mj, CodeMinor mn, int syserr) +CUDT::APIError::APIError(CodeMajor mj, CodeMinor mn, int syserr) { SetThreadLocalError(CUDTException(mj, mn, syserr)); } -srt::CUDT::APIError::APIError(int errorcode) +CUDT::APIError::APIError(int errorcode) { CodeMajor mj = CodeMajor(errorcode / 1000); CodeMinor mn = CodeMinor(errorcode % 1000); @@ -3923,7 +3928,7 @@ srt::CUDT::APIError::APIError(int errorcode) // This doesn't have argument of GroupType due to header file conflicts. // [[using locked(s_UDTUnited.m_GlobControlLock)]] -srt::CUDTGroup& srt::CUDT::newGroup(const int type) +CUDTGroup& CUDT::newGroup(const int type) { const SRTSOCKET id = uglobal().generateSocketID(true); @@ -3931,11 +3936,11 @@ srt::CUDTGroup& srt::CUDT::newGroup(const int type) return uglobal().addGroup(id, SRT_GROUP_TYPE(type)).set_id(id); } -SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt) +SRTSOCKET CUDT::createGroup(SRT_GROUP_TYPE gt) { try { - srt::sync::ScopedLock globlock(uglobal().m_GlobControlLock); + sync::ScopedLock globlock(uglobal().m_GlobControlLock); return newGroup(gt).id(); // Note: potentially, after this function exits, the group // could be deleted, immediately, from a separate thread (tho @@ -3955,7 +3960,7 @@ SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt) // [[using locked(m_ControlLock)]] // [[using locked(CUDT::s_UDTUnited.m_GlobControlLock)]] -void srt::CUDTSocket::removeFromGroup(bool broken) +void CUDTSocket::removeFromGroup(bool broken) { CUDTGroup* g = m_GroupOf; if (g) @@ -3985,7 +3990,7 @@ void srt::CUDTSocket::removeFromGroup(bool broken) } } -SRTSOCKET srt::CUDT::getGroupOfSocket(SRTSOCKET socket) +SRTSOCKET CUDT::getGroupOfSocket(SRTSOCKET socket) { // Lock this for the whole function as we need the group // to persist the call. @@ -3997,14 +4002,14 @@ SRTSOCKET srt::CUDT::getGroupOfSocket(SRTSOCKET socket) return s->m_GroupOf->id(); } -SRTSTATUS srt::CUDT::getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize) +SRTSTATUS CUDT::getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize) { if (!CUDT::isgroup(groupid) || !psize) { return APIError(MJ_NOTSUP, MN_INVAL, 0); } - CUDTUnited::GroupKeeper k(uglobal(), groupid, CUDTUnited::ERH_RETURN); + CUDTUnited::GroupKeeper k(uglobal(), groupid, ERH_RETURN); if (!k.group) { return APIError(MJ_NOTSUP, MN_INVAL, 0); @@ -4015,7 +4020,7 @@ SRTSTATUS srt::CUDT::getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, s } #endif -SRTSTATUS srt::CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen) +SRTSTATUS CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen) { try { @@ -4049,7 +4054,7 @@ SRTSTATUS srt::CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen) } } -SRTSTATUS srt::CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock) +SRTSTATUS CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock) { try { @@ -4074,7 +4079,7 @@ SRTSTATUS srt::CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock) } } -SRTSTATUS srt::CUDT::listen(SRTSOCKET u, int backlog) +SRTSTATUS CUDT::listen(SRTSOCKET u, int backlog) { try { @@ -4095,7 +4100,7 @@ SRTSTATUS srt::CUDT::listen(SRTSOCKET u, int backlog) } } -SRTSOCKET srt::CUDT::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut) +SRTSOCKET CUDT::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut) { try { @@ -4119,7 +4124,7 @@ SRTSOCKET srt::CUDT::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t } } -SRTSOCKET srt::CUDT::accept(SRTSOCKET u, sockaddr* addr, int* addrlen) +SRTSOCKET CUDT::accept(SRTSOCKET u, sockaddr* addr, int* addrlen) { try { @@ -4143,7 +4148,7 @@ SRTSOCKET srt::CUDT::accept(SRTSOCKET u, sockaddr* addr, int* addrlen) } } -SRTSOCKET srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen) +SRTSOCKET CUDT::connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen) { try { @@ -4165,7 +4170,7 @@ SRTSOCKET srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, const sockaddr* } #if ENABLE_BONDING -SRTSOCKET srt::CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets[], int arraysize) +SRTSOCKET CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets[], int arraysize) { if (arraysize <= 0) return APIError(MJ_NOTSUP, MN_INVAL, 0), SRT_INVALID_SOCK; @@ -4178,7 +4183,7 @@ SRTSOCKET srt::CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets[], try { - CUDTUnited::GroupKeeper k(uglobal(), grp, CUDTUnited::ERH_THROW); + CUDTUnited::GroupKeeper k(uglobal(), grp, ERH_THROW); return uglobal().groupConnect(k.group, targets, arraysize); } catch (CUDTException& e) @@ -4197,7 +4202,7 @@ SRTSOCKET srt::CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets[], } #endif -SRTSOCKET srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) +SRTSOCKET CUDT::connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) { try { @@ -4218,7 +4223,7 @@ SRTSOCKET srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, int namelen, int } } -SRTSTATUS srt::CUDT::close(SRTSOCKET u, int reason) +SRTSTATUS CUDT::close(SRTSOCKET u, int reason) { try { @@ -4235,7 +4240,7 @@ SRTSTATUS srt::CUDT::close(SRTSOCKET u, int reason) } } -SRTSTATUS srt::CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen) +SRTSTATUS CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen) { try { @@ -4253,7 +4258,7 @@ SRTSTATUS srt::CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen) } } -SRTSTATUS srt::CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen) +SRTSTATUS CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen) { try { @@ -4271,7 +4276,7 @@ SRTSTATUS srt::CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen) } } -SRTSTATUS srt::CUDT::getsockdevname(SRTSOCKET u, char* name, size_t* namelen) +SRTSTATUS CUDT::getsockdevname(SRTSOCKET u, char* name, size_t* namelen) { try { @@ -4289,7 +4294,7 @@ SRTSTATUS srt::CUDT::getsockdevname(SRTSOCKET u, char* name, size_t* namelen) } } -SRTSTATUS srt::CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_optval, int* pw_optlen) +SRTSTATUS CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_optval, int* pw_optlen) { if (!pw_optval || !pw_optlen) { @@ -4301,13 +4306,13 @@ SRTSTATUS srt::CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_ #if ENABLE_BONDING if (CUDT::isgroup(u)) { - CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); + CUDTUnited::GroupKeeper k(uglobal(), u, ERH_THROW); k.group->getOpt(optname, (pw_optval), (*pw_optlen)); return SRT_STATUS_OK; } #endif - CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); + CUDT& udt = uglobal().locateSocket(u, ERH_THROW)->core(); udt.getOpt(optname, (pw_optval), (*pw_optlen)); return SRT_STATUS_OK; } @@ -4322,7 +4327,7 @@ SRTSTATUS srt::CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_ } } -SRTSTATUS srt::CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const void* optval, int optlen) +SRTSTATUS CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const void* optval, int optlen) { if (!optval || optlen < 0) return APIError(MJ_NOTSUP, MN_INVAL, 0); @@ -4332,13 +4337,13 @@ SRTSTATUS srt::CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const voi #if ENABLE_BONDING if (CUDT::isgroup(u)) { - CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); + CUDTUnited::GroupKeeper k(uglobal(), u, ERH_THROW); k.group->setOpt(optname, optval, optlen); return SRT_STATUS_OK; } #endif - CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); + CUDT& udt = uglobal().locateSocket(u, ERH_THROW)->core(); udt.setOpt(optname, optval, optlen); return SRT_STATUS_OK; } @@ -4353,7 +4358,7 @@ SRTSTATUS srt::CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const voi } } -int srt::CUDT::send(SRTSOCKET u, const char* buf, int len, int) +int CUDT::send(SRTSOCKET u, const char* buf, int len, int) { SRT_MSGCTRL mctrl = srt_msgctrl_default; return sendmsg2(u, buf, len, (mctrl)); @@ -4361,7 +4366,7 @@ int srt::CUDT::send(SRTSOCKET u, const char* buf, int len, int) // --> CUDT::recv moved down -int srt::CUDT::sendmsg(SRTSOCKET u, const char* buf, int len, int ttl, bool inorder, int64_t srctime) +int CUDT::sendmsg(SRTSOCKET u, const char* buf, int len, int ttl, bool inorder, int64_t srctime) { SRT_MSGCTRL mctrl = srt_msgctrl_default; mctrl.msgttl = ttl; @@ -4370,19 +4375,19 @@ int srt::CUDT::sendmsg(SRTSOCKET u, const char* buf, int len, int ttl, bool inor return sendmsg2(u, buf, len, (mctrl)); } -int srt::CUDT::sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& w_m) +int CUDT::sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& w_m) { try { #if ENABLE_BONDING if (CUDT::isgroup(u)) { - CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); + CUDTUnited::GroupKeeper k(uglobal(), u, ERH_THROW); return k.group->send(buf, len, (w_m)); } #endif - return uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core().sendmsg2(buf, len, (w_m)); + return uglobal().locateSocket(u, ERH_THROW)->core().sendmsg2(buf, len, (w_m)); } catch (const CUDTException& e) { @@ -4399,14 +4404,14 @@ int srt::CUDT::sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& w_m) } } -int srt::CUDT::recv(SRTSOCKET u, char* buf, int len, int) +int CUDT::recv(SRTSOCKET u, char* buf, int len, int) { SRT_MSGCTRL mctrl = srt_msgctrl_default; int ret = recvmsg2(u, buf, len, (mctrl)); return ret; } -int srt::CUDT::recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime) +int CUDT::recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime) { SRT_MSGCTRL mctrl = srt_msgctrl_default; int ret = recvmsg2(u, buf, len, (mctrl)); @@ -4414,19 +4419,19 @@ int srt::CUDT::recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime) return ret; } -int srt::CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_m) +int CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_m) { try { #if ENABLE_BONDING if (CUDT::isgroup(u)) { - CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); + CUDTUnited::GroupKeeper k(uglobal(), u, ERH_THROW); return k.group->recv(buf, len, (w_m)); } #endif - return uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core().recvmsg2(buf, len, (w_m)); + return uglobal().locateSocket(u, ERH_THROW)->core().recvmsg2(buf, len, (w_m)); } catch (const CUDTException& e) { @@ -4439,11 +4444,11 @@ int srt::CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_m) } } -int64_t srt::CUDT::sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) +int64_t CUDT::sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) { try { - CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); + CUDT& udt = uglobal().locateSocket(u, ERH_THROW)->core(); return udt.sendfile(ifs, offset, size, block); } catch (const CUDTException& e) @@ -4461,11 +4466,11 @@ int64_t srt::CUDT::sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t } } -int64_t srt::CUDT::recvfile(SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block) +int64_t CUDT::recvfile(SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block) { try { - return uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core().recvfile(ofs, offset, size, block); + return uglobal().locateSocket(u, ERH_THROW)->core().recvfile(ofs, offset, size, block); } catch (const CUDTException& e) { @@ -4478,7 +4483,7 @@ int64_t srt::CUDT::recvfile(SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t } } -int srt::CUDT::select(int, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout) +int CUDT::select(int, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout) { if ((!readfds) && (!writefds) && (!exceptfds)) { @@ -4504,7 +4509,7 @@ int srt::CUDT::select(int, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET } } -int srt::CUDT::selectEx(const vector& fds, +int CUDT::selectEx(const vector& fds, vector* readfds, vector* writefds, vector* exceptfds, @@ -4534,7 +4539,7 @@ int srt::CUDT::selectEx(const vector& fds, } } -int srt::CUDT::epoll_create() +int CUDT::epoll_create() { try { @@ -4551,7 +4556,7 @@ int srt::CUDT::epoll_create() } } -SRTSTATUS srt::CUDT::epoll_clear_usocks(int eid) +SRTSTATUS CUDT::epoll_clear_usocks(int eid) { try { @@ -4570,7 +4575,7 @@ SRTSTATUS srt::CUDT::epoll_clear_usocks(int eid) } } -SRTSTATUS srt::CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) +SRTSTATUS CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) { try { @@ -4588,7 +4593,7 @@ SRTSTATUS srt::CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int } } -SRTSTATUS srt::CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) +SRTSTATUS CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) { try { @@ -4606,7 +4611,7 @@ SRTSTATUS srt::CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int } } -SRTSTATUS srt::CUDT::epoll_update_usock(const int eid, const SRTSOCKET u, const int* events) +SRTSTATUS CUDT::epoll_update_usock(const int eid, const SRTSOCKET u, const int* events) { try { @@ -4625,7 +4630,7 @@ SRTSTATUS srt::CUDT::epoll_update_usock(const int eid, const SRTSOCKET u, const } } -SRTSTATUS srt::CUDT::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) +SRTSTATUS CUDT::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) { try { @@ -4644,7 +4649,7 @@ SRTSTATUS srt::CUDT::epoll_update_ssock(const int eid, const SYSSOCKET s, const } } -SRTSTATUS srt::CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u) +SRTSTATUS CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u) { try { @@ -4663,7 +4668,7 @@ SRTSTATUS srt::CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u) } } -SRTSTATUS srt::CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s) +SRTSTATUS CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s) { try { @@ -4682,7 +4687,7 @@ SRTSTATUS srt::CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s) } } -int srt::CUDT::epoll_wait(const int eid, +int CUDT::epoll_wait(const int eid, set* readfds, set* writefds, int64_t msTimeOut, @@ -4704,7 +4709,7 @@ int srt::CUDT::epoll_wait(const int eid, } } -int srt::CUDT::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) +int CUDT::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { try { @@ -4721,7 +4726,7 @@ int srt::CUDT::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, } } -int32_t srt::CUDT::epoll_set(const int eid, int32_t flags) +int32_t CUDT::epoll_set(const int eid, int32_t flags) { try { @@ -4738,7 +4743,7 @@ int32_t srt::CUDT::epoll_set(const int eid, int32_t flags) } } -SRTSTATUS srt::CUDT::epoll_release(const int eid) +SRTSTATUS CUDT::epoll_release(const int eid) { try { @@ -4756,12 +4761,12 @@ SRTSTATUS srt::CUDT::epoll_release(const int eid) } } -srt::CUDTException& srt::CUDT::getlasterror() +CUDTException& CUDT::getlasterror() { return GetThreadLocalError(); } -SRTSTATUS srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instantaneous) +SRTSTATUS CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instantaneous) { #if ENABLE_BONDING if (CUDT::isgroup(u)) @@ -4770,7 +4775,7 @@ SRTSTATUS srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool in try { - CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); + CUDT& udt = uglobal().locateSocket(u, ERH_THROW)->core(); udt.bstats(perf, clear, instantaneous); return SRT_STATUS_OK; } @@ -4786,11 +4791,11 @@ SRTSTATUS srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool in } #if ENABLE_BONDING -SRTSTATUS srt::CUDT::groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear) +SRTSTATUS CUDT::groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear) { try { - CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); + CUDTUnited::GroupKeeper k(uglobal(), u, ERH_THROW); k.group->bstatsSocket(perf, clear); return SRT_STATUS_OK; } @@ -4808,11 +4813,11 @@ SRTSTATUS srt::CUDT::groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear } #endif -srt::CUDT* srt::CUDT::getUDTHandle(SRTSOCKET u) +CUDT* CUDT::getUDTHandle(SRTSOCKET u) { try { - return &uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); + return &uglobal().locateSocket(u, ERH_THROW)->core(); } catch (const CUDTException& e) { @@ -4827,7 +4832,7 @@ srt::CUDT* srt::CUDT::getUDTHandle(SRTSOCKET u) } } -vector srt::CUDT::existingSockets() +vector CUDT::existingSockets() { vector out; for (CUDTUnited::sockets_t::iterator i = uglobal().m_Sockets.begin(); i != uglobal().m_Sockets.end(); ++i) @@ -4837,14 +4842,14 @@ vector srt::CUDT::existingSockets() return out; } -SRT_SOCKSTATUS srt::CUDT::getsockstate(SRTSOCKET u) +SRT_SOCKSTATUS CUDT::getsockstate(SRTSOCKET u) { try { #if ENABLE_BONDING if (CUDT::isgroup(u)) { - CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); + CUDTUnited::GroupKeeper k(uglobal(), u, ERH_THROW); return k.group->getStatus(); } #endif @@ -4863,12 +4868,12 @@ SRT_SOCKSTATUS srt::CUDT::getsockstate(SRTSOCKET u) } } -int srt::CUDT::getMaxPayloadSize(SRTSOCKET id) +int CUDT::getMaxPayloadSize(SRTSOCKET id) { return uglobal().getMaxPayloadSize(id); } -int srt::CUDTUnited::getMaxPayloadSize(SRTSOCKET id) +int CUDTUnited::getMaxPayloadSize(SRTSOCKET id) { CUDTSocket* s = locateSocket(id); if (!s) @@ -4904,7 +4909,7 @@ int srt::CUDTUnited::getMaxPayloadSize(SRTSOCKET id) return payload_size; } -std::string srt::CUDTUnited::testSocketsClear() +std::string CUDTUnited::testSocketsClear() { std::ostringstream out; @@ -4930,6 +4935,8 @@ std::string srt::CUDTUnited::testSocketsClear() return out.str(); } +} + //////////////////////////////////////////////////////////////////////////////// namespace UDT diff --git a/srtcore/api.h b/srtcore/api.h index 3095db37b..793ea5c52 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -254,12 +254,6 @@ class CUDTUnited static const size_t MAX_CLOSE_RECORD_SIZE = 10; public: - enum ErrorHandling - { - ERH_RETURN, - ERH_THROW, - ERH_ABORT - }; static std::string CONID(SRTSOCKET sock); /// initialize the UDT library. @@ -497,48 +491,6 @@ class CUDTUnited void stopGarbageCollector(); void closeAllSockets(); -public: - struct SocketKeeper - { - CUDTSocket* socket; - - SocketKeeper(): socket(NULL) {} - - // This is intended for API functions to lock the socket's existence - // for the lifetime of their call. - SocketKeeper(CUDTUnited& glob, SRTSOCKET id, ErrorHandling erh = ERH_RETURN) { socket = glob.locateAcquireSocket(id, erh); } - - // This is intended for TSBPD thread that should lock the socket's - // existence until it exits. - SocketKeeper(CUDTUnited& glob, CUDTSocket* s) - { - acquire(glob, s); - } - - // Note: acquire doesn't check if the keeper already keeps anything. - // This is only for a use together with an empty constructor. - bool acquire(CUDTUnited& glob, CUDTSocket* s) - { - if (s == NULL) - { - socket = NULL; - return false; - } - - const bool caught = glob.acquireSocket(s); - socket = caught ? s : NULL; - return caught; - } - - ~SocketKeeper() - { - if (socket) - { - SRT_ASSERT(socket->isStillBusy() > 0); - socket->apiRelease(); - } - } - }; private: diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 9661048b0..d2dd11300 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -86,8 +86,8 @@ CSndBuffer::CSndBuffer(int ip_family, int size, int maxpld, int authtag) m_pBuffer->m_pNext = NULL; // circular linked list for out bound packets - m_pBlock = new Block; - Block* pb = m_pBlock; + m_pBlock = new CSndBlock; + CSndBlock* pb = m_pBlock; char* pc = m_pBuffer->m_pcData; for (int i = 0; i < m_iSize; ++i) @@ -98,7 +98,7 @@ CSndBuffer::CSndBuffer(int ip_family, int size, int maxpld, int authtag) if (i < m_iSize - 1) { - pb->m_pNext = new Block; + pb->m_pNext = new CSndBlock; pb = pb->m_pNext; } } @@ -111,10 +111,10 @@ CSndBuffer::CSndBuffer(int ip_family, int size, int maxpld, int authtag) CSndBuffer::~CSndBuffer() { - Block* pb = m_pBlock->m_pNext; + CSndBlock* pb = m_pBlock->m_pNext; while (pb != m_pBlock) { - Block* temp = pb; + CSndBlock* temp = pb; pb = pb->m_pNext; delete temp; } @@ -169,7 +169,7 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) // If there's more than one packet, this function must increase it by itself // and then return the accordingly modified sequence number in the reference. - Block* s = m_pLastBlock; + CSndBlock* s = m_pLastBlock; if (w_msgno == SRT_MSGNO_NONE) // DEFAULT-UNCHANGED msgno supplied { @@ -257,7 +257,7 @@ int CSndBuffer::addBufferFromFile(fstream& ifs, int len) log << CONID() << "addBufferFromFile: adding " << iPktLen << " packets (" << len << " bytes) to send, msgno=" << m_iNextMsgNo); - Block* s = m_pLastBlock; + CSndBlock* s = m_pLastBlock; int total = 0; for (int i = 0; i < iNumBlocks; ++i) { @@ -342,7 +342,7 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, m_pCurrBlock->m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(kflgs); } - Block* p = m_pCurrBlock; + CSndBlock* p = m_pCurrBlock; w_packet.set_msgflags(m_pCurrBlock->m_iMsgNoBitset); w_srctime = m_pCurrBlock->m_tsOriginTime; m_pCurrBlock = m_pCurrBlock->m_pNext; @@ -380,7 +380,7 @@ int32_t CSndBuffer::getMsgNoAt(const int offset) { ScopedLock bufferguard(m_BufLock); - Block* p = m_pFirstBlock; + CSndBlock* p = m_pFirstBlock; if (p) { @@ -400,7 +400,7 @@ int32_t CSndBuffer::getMsgNoAt(const int offset) // XXX Suboptimal procedure to keep the blocks identifiable // by sequence number. Consider using some circular buffer. int i; - Block* ee SRT_ATR_UNUSED = 0; + CSndBlock* ee SRT_ATR_UNUSED = 0; for (i = 0; i < offset && p; ++i) { ee = p; @@ -422,6 +422,49 @@ int32_t CSndBuffer::getMsgNoAt(const int offset) return p->getMsgSeq(); } +bool CSndBuffer::getPacketRangeSize(int32_t seqlo, int32_t seqhi, int& w_packets, int& w_bytes) +{ + ScopedLock bufferguard(m_BufLock); + + + int npackets = 0, nbytes = 0; + + // XXX Suboptimal procedure to keep the blocks identifiable + // by sequence number. Consider using some circular buffer. + CSndBlock* p = m_pFirstBlock; + for ( ; p != m_pLastBlock; p = p->m_pNext) + { + if (p->m_iSeqNo == seqlo) + break; + } + if (p == m_pLastBlock) + return false; + + for ( ; p != m_pLastBlock; p = p->m_pNext) + { + ++npackets; + nbytes += p->m_iLength; + if (p->m_iSeqNo == seqhi) + break; + } + + w_packets = npackets; + w_bytes = nbytes; + + if (p == m_pLastBlock) + { + if (p->m_iSeqNo == seqhi) + return true; + + // This means it caught some initial packets, but didn't reach + // the end of range. This way it will have some packets, but + // this was an error + return false; + } + + return true; +} + int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) { // NOTE: w_packet.m_iSeqNo is expected to be set to the value @@ -429,7 +472,7 @@ int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time ScopedLock bufferguard(m_BufLock); - Block* p = m_pFirstBlock; + CSndBlock* p = m_pFirstBlock; // XXX Suboptimal procedure to keep the blocks identifiable // by sequence number. Consider using some circular buffer. @@ -532,7 +575,7 @@ int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time sync::steady_clock::time_point CSndBuffer::getPacketRexmitTime(const int offset) { ScopedLock bufferguard(m_BufLock); - const Block* p = m_pFirstBlock; + const CSndBlock* p = m_pFirstBlock; // XXX Suboptimal procedure to keep the blocks identifiable // by sequence number. Consider using some circular buffer. @@ -720,20 +763,20 @@ void CSndBuffer::increase() p->m_pNext = nbuf; // new packet blocks - Block* nblk = NULL; + CSndBlock* nblk = NULL; try { - nblk = new Block; + nblk = new CSndBlock; } catch (...) { delete nblk; throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } - Block* pb = nblk; + CSndBlock* pb = nblk; for (int i = 1; i < unitsize; ++i) { - pb->m_pNext = new Block; + pb->m_pNext = new CSndBlock; pb = pb->m_pNext; } diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index 9e47d483e..84b6163b9 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -71,6 +71,30 @@ modified by namespace srt { +struct CSndBlock +{ + typedef sync::steady_clock::time_point time_point; + char* m_pcData; // pointer to the data block + int m_iLength; // payload length of the block (excluding auth tag). + + int32_t m_iMsgNoBitset; // message number and special bit flags + int32_t m_iSeqNo; // sequence number for scheduling + time_point m_tsOriginTime; // block origin time (either provided from above or equals the time a message was submitted for sending. + time_point m_tsRexmitTime; // packet retransmission time + int m_iTTL; // time to live (milliseconds) + + CSndBlock* m_pNext; // next block + + int32_t getMsgSeq() + { + // NOTE: this extracts message ID with regard to REXMIT flag. + // This is valid only for message ID that IS GENERATED in this instance, + // not provided by the peer. This can be otherwise sent to the peer - it doesn't matter + // for the peer that it uses LESS bits to represent the message. + return m_iMsgNoBitset & MSGNO_SEQ::mask; + } +}; + class CSndBuffer { typedef sync::steady_clock::time_point time_point; @@ -183,6 +207,7 @@ class CSndBuffer int getAvgBufSize(int& bytes, int& timespan); int getCurrBufSize(int& bytes, int& timespan) const; + bool getPacketRangeSize(int32_t seqlo, int32_t seqhi, int& w_packets, int& w_bytes); /// Het maximum payload length per packet. int getMaxPacketLen() const; @@ -219,30 +244,10 @@ class CSndBuffer private: mutable sync::Mutex m_BufLock; // used to synchronize buffer operation - - struct Block - { - char* m_pcData; // pointer to the data block - int m_iLength; // payload length of the block (excluding auth tag). - - int32_t m_iMsgNoBitset; // message number - int32_t m_iSeqNo; // sequence number for scheduling - time_point m_tsOriginTime; // block origin time (either provided from above or equals the time a message was submitted for sending. - time_point m_tsRexmitTime; // packet retransmission time - int m_iTTL; // time to live (milliseconds) - - Block* m_pNext; // next block - - int32_t getMsgSeq() - { - // NOTE: this extracts message ID with regard to REXMIT flag. - // This is valid only for message ID that IS GENERATED in this instance, - // not provided by the peer. This can be otherwise sent to the peer - it doesn't matter - // for the peer that it uses LESS bits to represent the message. - return m_iMsgNoBitset & MSGNO_SEQ::mask; - } - - } * m_pBlock, *m_pFirstBlock, *m_pCurrBlock, *m_pLastBlock; + CSndBlock* m_pBlock; + CSndBlock* m_pFirstBlock; + CSndBlock* m_pCurrBlock; + CSndBlock* m_pLastBlock; // m_pBlock: The head pointer // m_pFirstBlock: The first block diff --git a/srtcore/common.cpp b/srtcore/common.cpp index 9c4ddf18d..6f1854b4c 100644 --- a/srtcore/common.cpp +++ b/srtcore/common.cpp @@ -70,6 +70,7 @@ modified by #endif #include "udt.h" +#include "api.h" #include "md5.h" #include "common.h" #include "netinet_any.h" @@ -595,6 +596,7 @@ vector GetLocalInterfaces() return locals; } +SRTSOCKET SocketKeeper::id() const { return socket ? socket->id() : SRT_INVALID_SOCK; } } // namespace srt diff --git a/srtcore/common.h b/srtcore/common.h index 3e6097113..a892c2f8c 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -101,6 +101,17 @@ modified by namespace srt { +// export import .api default; +class CUDTUnited; +class CUDTSocket; + +enum ErrorHandling +{ + ERH_RETURN, + ERH_THROW, + ERH_ABORT +}; + struct CNetworkInterface { sockaddr_any address; @@ -127,6 +138,39 @@ struct CNetworkInterface } }; +struct SocketKeeper +{ + CUDTSocket* socket; + CUDTUnited& glob; + + SocketKeeper(CUDTUnited& go, CUDTSocket* p = NULL): socket(p), glob(go) {} + + SocketKeeper(const SocketKeeper& r): socket(r.socket), glob(r.glob) + { + acquire_socket(socket); + } + + SocketKeeper& operator=(const SocketKeeper& r) + { + // Assume the object could not be created without glob. + socket = r.socket; + acquire_socket(socket); + return *this; + } + + ~SocketKeeper() + { + release_socket(socket); + } + + SRTSOCKET id() const; + +private: + static void acquire_socket(CUDTSocket* s); + static void release_socket(CUDTSocket* s); +}; + + } namespace srt_logging diff --git a/srtcore/core.cpp b/srtcore/core.cpp index ab408c048..b27fa9995 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -262,6 +262,53 @@ CUDTUnited& srt::CUDT::uglobal() #endif +srt::SocketKeeper srt::CUDT::keep(srt::CUDTSocket* s) +{ + SocketKeeper k(uglobal()); + if (s == NULL || !uglobal().acquireSocket(s)) + { + return k; + } + + k.socket = s; + return k; +} + +srt::SocketKeeper srt::CUDT::keep(SRTSOCKET id, ErrorHandling erh) +{ + return SocketKeeper (uglobal(), uglobal().locateAcquireSocket(id, erh)); +} + +void srt::SocketKeeper::acquire_socket(CUDTSocket* s) +{ + // This is an internal function called in the copy constructor. + // ASSUMES the socket exists and is already kept by another + // SocketKeeper, so no official acquisition is done, just + // increase the counter. + SRT_ASSERT(s); + if (s) + { + SRT_ASSERT(s->isStillBusy() > 0); + s->apiAcquire(); + } +} + +// NOTE: This is an object that is being kept alive in the central +// database. The release action may turn the counter to 0, but +// this object shall not do anything about this. This is only an +// information for the central database that it is now free to delete +// the socket when it sees it fit. Busy counter only prevents the +// central database from doing it. +void srt::SocketKeeper::release_socket(CUDTSocket* s) +{ + SRT_ASSERT(s); + if (s) + { + SRT_ASSERT(s->isStillBusy() > 0); + s->apiRelease(); + } +} + void srt::CUDT::construct() { m_pSndBuffer = NULL; @@ -994,7 +1041,7 @@ void srt::CUDT::open() m_tsNextACKTime.store(currtime + m_tdACKInterval); m_tsNextNAKTime.store(currtime + m_tdNAKInterval); m_tsLastRspAckTime = currtime; - m_tsLastSndTime.store(currtime); + m_LastSend.reset(currtime); #if ENABLE_BONDING m_tsUnstableSince = steady_clock::time_point(); @@ -6082,7 +6129,7 @@ SRT_REJECT_REASON srt::CUDT::setupCC() m_tsNextACKTime.store(currtime + m_tdACKInterval); m_tsNextNAKTime.store(currtime + m_tdNAKInterval); m_tsLastRspAckTime = currtime; - m_tsLastSndTime.store(currtime); + m_LastSend.reset(currtime); HLOGC(rslog.Debug, log << CONID() << "setupCC: setting parameters: mss=" << m_config.iMSS << " maxCWNDSize/FlowWindowSize=" @@ -6772,11 +6819,12 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) size = min(len, sndBuffersLeft() * m_iMaxSRTPayloadSize); } + int32_t seqno; { ScopedLock recvAckLock(m_RecvAckLock); // insert the user buffer into the sending list - int32_t seqno = m_iSndNextSeqNo; + seqno = m_iSndNextSeqNo; IF_HEAVY_LOGGING(int32_t orig_seqno = seqno); IF_HEAVY_LOGGING(steady_clock::time_point ts_srctime = steady_clock::time_point() + microseconds_from(w_mctrl.srctime)); @@ -6852,9 +6900,19 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) } } - // Insert this socket to the snd list if it is not on the list already. - // m_pMuxer->sndUList()->pop may lock CSndUList::m_ListLock and then m_RecvAckLock - m_pMuxer->updateSendNormal(m_parent); + if (m_config.uSenderMode == 0) + { + // Insert this socket to the snd list if it is not on the list already. + // m_pMuxer->sndUList()->pop may lock CSndUList::m_ListLock and then m_RecvAckLock + m_pMuxer->updateSendNormal(m_parent); + } + else + { + if (m_LastSched.kickSchedule(seqno, steady_clock::now(), m_tdSendInterval)) + { + m_pMuxer->scheduleSend(m_parent, seqno, sched::TP_REGULAR, m_LastSched.lastTime()); + } + } #ifdef SRT_ENABLE_ECN // IF there was a packet drop on the sender side, report congestion to the app. @@ -6869,6 +6927,20 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) return size; } +sync::steady_clock::time_point srt::CUDT::calculateRegularSchedTime() +{ + time_point last = m_LastSched.lastTime(); + + time_point now = steady_clock::now(); + if (now - last > seconds_from(1)) + { + // If the sending event is 1 second old, return current time. + return now; + } + + return last + m_tdSendInterval; +} + int srt::CUDT::recv(char* data, int len) { SRT_MSGCTRL mctrl = srt_msgctrl_default; @@ -7213,6 +7285,12 @@ int64_t srt::CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int blo throw CUDTException(MJ_SETUP, MN_SECURITY, 0); } + if (m_config.uSenderMode != 0) + { + LOGC(aslog.Error, log << CONID() << "In FILE mode the scheduled sender mode is not supported"); + throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI); + } + ScopedLock sendguard (m_SendLock); if (m_pSndBuffer->getCurrBufSize() == 0) @@ -7984,7 +8062,7 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp // Fix keepalive if (nbsent) - m_tsLastSndTime.store(steady_clock::now()); + m_LastSend.update(steady_clock::now(), nbsent); } bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) @@ -8373,7 +8451,9 @@ void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) #endif // insert this socket to snd list if it is not on the list yet - const steady_clock::time_point currtime = m_pMuxer->updateSendNormal(m_parent); + const steady_clock::time_point currtime = (m_config.uSenderMode == 0) + ? m_pMuxer->updateSendNormal(m_parent) + : steady_clock::now(); if (m_config.bSynSending) { @@ -8483,7 +8563,9 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ const int cwnd = std::min(m_iFlowWindowSize, m_iCongestionWindow); if (bWasStuck && cwnd > getFlightSpan()) { - m_pMuxer->updateSendNormal(m_parent); + if (m_config.uSenderMode == 0) + m_pMuxer->updateSendNormal(m_parent); + HLOGC(gglog.Debug, log << CONID() << "processCtrlAck: could reschedule SND. iFlowWindowSize " << m_iFlowWindowSize << " SPAN " << getFlightSpan() << " ackdataseqno %" << ackdata_seqno); @@ -8761,6 +8843,10 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) // when logging is forcefully off. int32_t wrong_loss SRT_ATR_UNUSED = SRT_SEQNO_NONE; + // Will be used to determine rexmit packets to schedule. + // If remain with this value, there's nothing to schedule. + int32_t sched_lo = SRT_SEQNO_NONE, sched_hi = SRT_SEQNO_NONE; + // protect packet retransmission { ScopedLock ack_lock(m_RecvAckLock); @@ -8800,6 +8886,8 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << losslist_lo << " - " << losslist_hi << " to loss list"); num = m_pSndLossList->insert(losslist_lo, losslist_hi); + sched_lo = losslist_lo; + sched_hi = losslist_hi; } // ELSE losslist_lo %< m_iSndLastAck else @@ -8826,6 +8914,9 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi); dropreq_hi = CSeqNo::decseq(m_iSndLastAck); IF_HEAVY_LOGGING(drop_type = "partially"); + + sched_lo = m_iSndLastAck; + sched_hi = losslist_hi; } // In distinction to losslist, DROPREQ has always just one range, @@ -8866,6 +8957,8 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding %" << losslist[i] << " (1 packet) to loss list"); const int num = m_pSndLossList->insert(losslist[i], losslist[i]); + sched_lo = losslist[i]; + sched_hi = losslist[i]; enterCS(m_StatsLock); m_stats.sndr.lost.count(num); @@ -8887,6 +8980,37 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) } } + if (m_config.uSenderMode == 1) + { + int npackets = 0, nbytes = 0; + if (!m_pSndBuffer->getPacketRangeSize(sched_lo, sched_hi, (npackets), (nbytes))) + { + // XXX LOGC + } + + time_point start; + duration step; + if (npackets && defineSchedTimes(sched_lo, sched_hi, (start), (step))) + { + int32_t seqno = sched_lo, endseq = CSeqNo::incseq(sched_hi); + + time_point when = start; + for (;;) + { + m_pMuxer->scheduleSend(m_parent, seqno, sched::TP_REXMIT, when); + seqno = CSeqNo::incseq(seqno); + if (seqno == endseq) + { + break; + } + when += step; + } + + // After scheduling update the send time stats. + m_LastSend.update(when, nbytes, npackets); + } + } + updateCC(TEV_LOSSREPORT, EventVariant(losslist, losslist_len)); if (!secure) @@ -8911,11 +9035,12 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) // But in the live mode with too many lost packets it could result in // holding head of the line for too long, while in file mode it doesn't // really improve the transmission efficiency (because it doesn't use - // NAKREPORT (so the receiver won't repeat lossreport command), and the + // NAKREPORT, so the receiver won't repeat lossreport command), and the // blind rexmit mode is laterexmit (the sender will repeat sending // unacknowledged packets only when it has reached the limit with // nothing more to withdraw from the sender buffer). - m_pMuxer->updateSendNormal(m_parent); + if (m_config.uSenderMode == 0) + m_pMuxer->updateSendNormal(m_parent); enterCS(m_StatsLock); m_stats.sndr.recvdNak.count(1); @@ -9025,7 +9150,7 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) const int nbsent = channel()->sendto(m_PeerAddr, rsppkt, m_SourceAddr); if (nbsent) { - m_tsLastSndTime.store(steady_clock::now()); + m_LastSend.update(steady_clock::now(), nbsent); } } } @@ -9751,7 +9876,7 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime #endif // Fix keepalive - m_tsLastSndTime.store(enter_time); + m_LastSend.update(enter_time, payload); considerLegacySrtHandshake(steady_clock::time_point()); @@ -9820,6 +9945,104 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime return payload >= 0; // XXX shouldn't be > 0 ? == 0 is only when buffer range exceeded. } +// Second version of packData: pack the exact data as specified in the specification +// reported from the schedule. +bool srt::CUDT::packData(const SchedPacket& spec, CPacket& w_packet, CNetworkInterface& w_src_addr) +{ + int payload = 0; + bool new_packet_packed = false; + + ScopedLock connectguard(m_ConnectionLock); + // If a closing action is done simultaneously, then + // m_bOpened should already be false, and it's set + // just before releasing this lock. + // + // If this lock is caught BEFORE the closing could + // start the dissolving process, this process will + // not be started until this function is finished. + if (!m_bOpened) + return false; + + time_point enter_time = steady_clock::now(); + w_src_addr = m_SourceAddr; + + IF_HEAVY_LOGGING(const char* reason); // The source of the data packet (normal/rexmit/filter) + if (spec.type() == sched::TP_REXMIT) + { + payload = packLostData((w_packet)); + IF_HEAVY_LOGGING(reason = "reXmit"); + } + else if (spec.type() == sched::TP_CONTROL) + { + if (m_PacketFilter.packControlPacket(spec.seqno(), m_pCryptoControl->getSndCryptoFlags(), (w_packet))) + { + HLOGC(qslog.Debug, log << CONID() << "filter: filter/CTL packet ready - packing instead of data."); + payload = (int) w_packet.getLength(); + IF_HEAVY_LOGGING(reason = "filter"); + + // Stats + ScopedLock lg(m_StatsLock); + m_stats.sndr.sentFilterExtra.count(1); + } + else + { + LOGC(qslog.Error, log << CONID() << "filter: IPE: didn't provide control packet for %" << m_iSndCurrSeqNo + << " that has been scheduled"); + } + } + else // type() == TP_REGULAR + { + if (!packUniqueData(w_packet)) + { + return false; + } + new_packet_packed = true; + + payload = (int) w_packet.getLength(); + IF_HEAVY_LOGGING(reason = "normal"); + } + + w_packet.set_id(m_PeerID); // Set the destination SRT socket ID. + + // XXX DELETE THIS. This should be done when scheduling the packet first time. + if (new_packet_packed && m_PacketFilter) + { + HLOGC(qslog.Debug, log << CONID() << "filter: Feeding packet for source clip"); + m_PacketFilter.feedSource((w_packet)); + } + +#if ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr() + HLOGC(qslog.Debug, + log << CONID() << "packData: " << reason << " packet seq=" << w_packet.seqno() << " (ACK=" << m_iSndLastAck + << " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << w_packet.MessageFlagStr() << ")"); +#endif + + // This means that the packet will really be sent. + if (payload >= 0) + { + // Fix keepalive + m_LastSend.update(enter_time, payload); + + considerLegacySrtHandshake(steady_clock::time_point()); + + // WARNING: TEV_SEND is the only event that is reported from + // the CSndQueue::worker thread. All others are reported from + // CRcvQueue::worker. If you connect to this signal, make sure + // that you are aware of prospective simultaneous access. + updateCC(TEV_SEND, EventVariant(&w_packet)); + + enterCS(m_StatsLock); + m_stats.sndr.sent.count(payload); + if (new_packet_packed) + m_stats.sndr.sentUnique.count(payload); + m_stats.sndr.updateRate(1, payload); + leaveCS(m_StatsLock); + return true; + } + + return false; +} + bool srt::CUDT::packUniqueData(CPacket& w_packet) { int current_sequence_number; // reflexing variable @@ -10478,6 +10701,8 @@ int srt::CUDT::processData(CUnit* in_unit) // Retransmitted and unordered packets do not provide expected measurement. // We expect the 16th and 17th packet to be sent regularly, // otherwise measurement must be rejected. + + // XXX NOTE: probe 16 shall NOT be executed in case of scheduler mode. m_RcvTimeWindow.probeArrival(packet, unordered || retransmitted); enterCS(m_StatsLock); @@ -11717,6 +11942,11 @@ void srt::CUDT::checkRexmitTimer(const steady_clock::time_point& currtime) log << CONID() << "ENFORCED " << (is_laterexmit ? "LATEREXMIT" : "FASTREXMIT") << " by ACK-TMOUT (scheduling): " << CSeqNo::incseq(m_iSndLastAck) << "-" << csn << " (" << CSeqNo::seqoff(m_iSndLastAck, csn) << " packets)"); + + if (m_config.uSenderMode != 0) + { + scheduleRexmitRange(m_iSndLastAck, csn); + } } } @@ -11726,7 +11956,65 @@ void srt::CUDT::checkRexmitTimer(const steady_clock::time_point& currtime) updateCC(TEV_CHECKTIMER, EventVariant(stage)); // schedule sending if not scheduled already - m_pMuxer->updateSendNormal(m_parent); + if (m_config.uSenderMode == 0) + { + m_pMuxer->updateSendNormal(m_parent); + } +} + +void srt::CUDT::scheduleRexmitRange(int32_t lo, int32_t hi) +{ + // First, measure the distance and see if you can gracefully + // schedule these packets without breaking the current bandwidth limit. + + time_point start; + duration step; + if (defineSchedTimes(lo, hi, (start), (step))) + { + int32_t seqno = lo, endseq = CSeqNo::incseq(hi); + + int npackets = 0, nbytes = 0; + if (!m_pSndBuffer->getPacketRangeSize(lo, hi, (npackets), (nbytes))) + { + // XXX LOGC + } + time_point when = start; + for (;;) + { + m_pMuxer->scheduleSend(m_parent, seqno, sched::TP_REXMIT, when); + seqno = CSeqNo::incseq(seqno); + if (seqno == endseq) + { + break; + } + when += step; + } + + // After scheduling update the send time stats. + m_LastSend.update(when, nbytes, npackets); + } +} + +// This function should check if all packets in the range can be retransmitted +// and whether they can be scheduled for that easily. Remarks: +// - This function is used only for FASTREXMIT or LATEREXMIT; so we can be certain +// that have NAKREPORT off, might be that TLPKTDROP is on, if so, TSBPDMODE is also on. +// - if TLPKTDROP is off, then all packets must be retransmitted, so this procedure must +// define scheduling time for all packets in the range no matter what. +// - if TLPKTDROP is on, DO NOT schedule these packets at all, if the next regular +// packet to send has a critically small expected arrival time (less than avg RTT) +bool srt::CUDT::defineSchedTimes(int32_t lo, int32_t hi, time_point& w_start, duration& w_step) +{ + // XXX THIS IS NOT READY YET. KINDA STUB. + + //int distance = CSeqNo::seqlen(lo, hi); + + // loss should get higher priority, so we + time_point start = m_LastSend.time(); + + w_start = start; + w_step = m_tdSendInterval; + return true; } void srt::CUDT::checkTimers() @@ -11756,7 +12044,7 @@ void srt::CUDT::checkTimers() // Check if FAST or LATE packet retransmission is required checkRexmitTimer(currtime); - if (currtime > m_tsLastSndTime.load() + microseconds_from(COMM_KEEPALIVE_PERIOD_US)) + if (currtime > m_LastSend.time() + microseconds_from(COMM_KEEPALIVE_PERIOD_US)) { sendCtrl(UMSG_KEEPALIVE); #if ENABLE_BONDING diff --git a/srtcore/core.h b/srtcore/core.h index a29d74b35..ae9fe2407 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -69,6 +69,7 @@ modified by #include "handshake.h" #include "congctl.h" #include "packetfilter.h" +#include "schedule_snd.h" #include "socketconfig.h" #include "utilities.h" #include "logger_defs.h" @@ -153,6 +154,210 @@ enum SeqPairItems class CCryptoControl; namespace srt { + +class CLastSend +{ + friend class CUDT; + sync::AtomicClock m_tsSendTime; + + // Statistical data, reset when sending ACKACK + sync::AtomicClock m_tsBeginTime; + sync::atomic m_uNumberPackets; + sync::atomic m_uNumberBytes; + uint32_t m_uBytesPerSecond; + + CLastSend(): m_uNumberPackets(0), m_uNumberBytes(0) + { + } + + sync::steady_clock::time_point time() const { return m_tsSendTime; } + + void reset(const sync::steady_clock::time_point& tm) + { + sync::steady_clock::time_point old = m_tsBeginTime; + m_tsBeginTime.store(m_tsSendTime.load()); + m_tsSendTime = tm; + m_uNumberBytes = 4; + m_uNumberPackets = 1; + + sync::steady_clock::duration diff = tm - m_tsBeginTime; + m_uBytesPerSecond = (m_uNumberBytes * 1000000) / count_milliseconds(diff); + } + + void update(const sync::steady_clock::time_point& tm, uint32_t bytes, uint32_t npackets = 1) + { + m_tsSendTime = tm; + // XXX IMPLEMENT R-M-W mode += operator for atomics!!! + m_uNumberBytes = m_uNumberBytes + bytes; + m_uNumberPackets = m_uNumberPackets + npackets; + } +}; + +class CLastSched +{ + friend class CUDT; + + sync::Mutex m_Lock; + sync::atomic m_iSchedSeqNo; // SEQNO up to which regular packets were scheduled + int32_t m_iBufferedSeqNo; // SEQNO up to which there are packets in the sender buffer + + sync::AtomicClock m_tsTime; + sync::AtomicDuration m_tdLastInterval; + + sync::atomic m_bChain; + +public: + + CLastSched() : m_iSchedSeqNo(SRT_SEQNO_NONE), m_iBufferedSeqNo(SRT_SEQNO_NONE), m_tdLastInterval(), m_bChain(false) {} + + sync::steady_clock::time_point lastTime() const { return m_tsTime.load(); } + bool shallChain() const { return m_bChain; } + void set_intervnal(const sync::steady_clock::duration& i) { m_tdLastInterval = i; } + int32_t lastSchedSeq() const { return m_iSchedSeqNo; } + + // This is to be called on adding a new packet to the sender buffer. + // We need: + // - buffered_end: sequence number of the just added packet + // - currtime: the current time when calling + // - interval: the scheduling interval at the current maximum allowed speed + // Return: + // - true: the caller should then enqueue the packet in the schedule as regular + // - false: do nothing; the sender worker will do the job as needed + bool kickSchedule(int32_t buffered_end, const sync::steady_clock::time_point& currtime, const sync::steady_clock::duration& interval) + { + sync::ScopedLock lk (m_Lock); + + if (m_iSchedSeqNo == SRT_SEQNO_NONE) + { + // Nothing was scheduled so far and buffered_end is the + // very first packet in the buffer (we schedule one packet + // at a time). + m_iBufferedSeqNo = buffered_end; + m_iSchedSeqNo = buffered_end; + m_tsTime = currtime; + + // Still false because we have just one packet here, + // so once this is scheduled, thereś nothing more. + m_bChain = false; + return true; + } + + // We had something scheduled earlier + bool empty = (m_iSchedSeqNo == m_iBufferedSeqNo); + m_iBufferedSeqNo = buffered_end; + + if (!empty && m_bChain) + { + // If it wasn't empty, rely on the next schedule + // from the worker thread (chain scheduling). + + // Also do not update the scheduling time - this + // will be in the hands of the worker thread. + return false; + } + + // Set the interval for the chain. The interval can be + // modified at any time later. + m_tdLastInterval = interval; + + // Here we know that the worker will not be chaining + // the schedule after the previous execution. We need + // to schedule. + + sync::steady_clock::time_point forfeiture = currtime - interval; + // Ok, note that the last schedule time can as well be in the future. + // The `interval` is the shortest possible time distance allowed + // to be kept between sent packets. That's why we need to have + // exactly 1s of positive distance so that the unused time forfeits. + if (forfeiture - m_tsTime > sync::seconds_from(1)) + { + // In case when the last send time recorded here is older by + // more than 1 second from the current time carried back by 1 + // interval, forfeit that time and update to that "earliest possible". + m_tsTime = forfeiture; + } + else + { + // If the last sending time was even in the past, but less than + // 1 second behind the current time, just increase the sending + // time by interval. + m_tsTime = m_tsTime.load() + interval; + } + + m_iSchedSeqNo = CSeqNo::incseq(m_iSchedSeqNo); + + // If equal, it means that we DO WANT to schedule the earliest + // handing packet, but the worker should not try to chain + // the next one. + m_bChain = (m_iSchedSeqNo != m_iBufferedSeqNo); + return true; + } + + // The function to be called from the sender worker. + // It has just executed schedule for a given regular packet + // and has to schedule sending the next one. + // Returns false if it turned to the last one and should not chain next one. + bool chainSchedule(int32_t last_seqno, const sync::steady_clock::time_point& exec_time) + { + // Note: exec_time is the expected time when the next + // packet should be scheduled. + sync::ScopedLock lk (m_Lock); + + // We state that m_iSeqNo is set already, as it should have been + // set by the main thread scheduler (after adding buffer). We + // need to only check if there's anything new to schedule. + // The lock is applied to prevent the simultaneous regular packet + // checker to change the state when trying to add a new packet. + + // If reached the end (no new regular packets eligible for scheduling) + // do nothing and return false. JUST IN CASE (the call shall not happen + // in such case). + if (m_iSchedSeqNo == m_iBufferedSeqNo || !m_bChain) + return false; + + if (last_seqno != m_iSchedSeqNo) + { + // XXX ERROR jump-over schedule + if (CSeqNo::seqcmp(last_seqno, m_iBufferedSeqNo) > 0) + { + // XXX ERROR has scheduled packet not added to the buffer + // Just stop the schedule and wait for the API function + // to reinstate it + m_iSchedSeqNo = m_iBufferedSeqNo; + m_bChain = false; + return false; + } + + m_iSchedSeqNo = last_seqno; + } + + // Ok, we have one packet to schedule. + // The time passed as exec_time already involves interval and it should + // be calculated by the caller basing on lastTime() returned from here. + // Note that the API function modifies this time only when it schedules + // the packet, while worker doesn't. + + int32_t newseq = CSeqNo::incseq(m_iSchedSeqNo); + + if (newseq == m_iBufferedSeqNo) + { + // After this one, there are no more packets to schedule ATM. + // So turn off chaining. The API call will reinstate it on the + // next call. + m_bChain = false; + + // NOTE: if m_bChain == false, this function shall not have + // been called + } + + // We do want to schedule this one, so record the data + m_iSchedSeqNo = newseq; + m_tsTime = exec_time; + return true; + } + +}; + class CUDTUnited; class CUDTSocket; #if ENABLE_BONDING @@ -458,6 +663,9 @@ class CUDT static CUDTUnited& uglobal(); // UDT global management base + static SocketKeeper keep(CUDTSocket* s); + static SocketKeeper keep(SRTSOCKET, ErrorHandling erh = ERH_THROW); + std::set& pollset() { return m_sPollID; } CSrtConfig m_config; @@ -736,7 +944,10 @@ class CUDT SRT_ATTR_EXCLUDES(m_ConnectionLock) void checkSndTimers(); - + + // For schedule mode sending + sync::steady_clock::time_point calculateRegularSchedTime(); + /// @brief Check and perform KM refresh if needed. void checkSndKMRefresh(); @@ -892,7 +1103,7 @@ class CUDT CSndRateEstimator m_SndRexmitRate; // Retransmission rate estimation. #endif - atomic_duration m_tdSendInterval; // Inter-packet time, in CPU clock cycles + atomic_duration m_tdSendInterval; // Inter-packet time according to the current bandwidth limit atomic_duration m_tdSendTimeDiff; // Aggregate difference in inter-packet sending time @@ -910,7 +1121,7 @@ class CUDT SRT_ATTR_GUARDED_BY(m_RecvAckLock) atomic_time_point m_tsLastRspTime; // Timestamp of last response from the peer time_point m_tsLastRspAckTime; // (SND) Timestamp of last ACK from the peer - atomic_time_point m_tsLastSndTime; // Timestamp of last data/ctrl sent (in system ticks) + CLastSend m_LastSend; // Time and stats for the last sending time_point m_tsLastWarningTime; // Last time that a warning message is sent atomic_time_point m_tsLastReqTime; // last time when a connection request is sent time_point m_tsRcvPeerStartTime; @@ -922,7 +1133,8 @@ class CUDT int m_iPktCount; // Packet counter for ACK int m_iLightACKCount; // Light ACK counter - time_point m_tsNextSendTime; // Scheduled time of next packet sending + time_point m_tsNextSendTime; // Scheduled time of next packet sending (normal mode) + CLastSched m_LastSched; // Data for packet scheduling (scheduler mode) sync::atomic m_iSndLastFullAck; // Last full ACK received SRT_ATTR_GUARDED_BY(m_RecvAckLock) @@ -1160,6 +1372,11 @@ class CUDT /// @retval false Nothing was extracted for sending, @a nexttime should be ignored bool packData(CPacket& packet, time_point& nexttime, CNetworkInterface& src_addr); + bool packData(const SchedPacket& spec, CPacket& packet, CNetworkInterface& src_addr); + + bool defineSchedTimes(int32_t lo, int32_t hi, time_point& w_start, duration& w_step); + void scheduleRexmitRange(int32_t lo, int32_t hi); + /// Also excludes srt::CUDTUnited::m_GlobControlLock. SRT_ATTR_EXCLUDES(m_RcvTsbPdStartupLock, m_StatsLock, m_RecvLock, m_RcvLossLock, m_RcvBufferLock) int processData(CUnit* unit); diff --git a/srtcore/filelist.maf b/srtcore/filelist.maf index e801c8b7f..af0debc4a 100644 --- a/srtcore/filelist.maf +++ b/srtcore/filelist.maf @@ -22,6 +22,7 @@ packet.cpp packetfilter.cpp queue.cpp congctl.cpp +schedule_snd.cpp socketconfig.cpp srt_c_api.cpp srt_compat.c diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 89577cb03..abd144bf6 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -867,7 +867,7 @@ void CUDTGroup::getOpt(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) enterCS(m_GroupLock); gli_t gi = m_Group.begin(); CUDTSocket* const ps = (gi != m_Group.end()) ? gi->ps : NULL; - CUDTUnited::SocketKeeper sk(CUDT::uglobal(), ps); + SocketKeeper sk = CUDT::keep(ps); leaveCS(m_GroupLock); if (sk.socket) { @@ -2513,7 +2513,7 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) << " time=" << FormatTime(infoToRead.tsbpd_time)); } - const int res = socketToRead->core().receiveMessage((buf), len, (w_mc), CUDTUnited::ERH_RETURN); + const int res = socketToRead->core().receiveMessage((buf), len, (w_mc), ERH_RETURN); HLOGC(grlog.Debug, log << "grp/recv: $" << id() << ": @" << socketToRead->core().m_SocketID << ": Extracted data with %" << w_mc.pktseq << " #" << w_mc.msgno << ": " << (res <= 0 ? "(NOTHING)" : BufferStamp(buf, res))); @@ -3578,7 +3578,7 @@ void CUDTGroup::sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx if (i->second & SRT_EPOLL_ERR) { SRTSOCKET id = i->first; - CUDTSocket* s = m_Global.locateSocket(id, CUDTUnited::ERH_RETURN); // << LOCKS m_GlobControlLock! + CUDTSocket* s = m_Global.locateSocket(id, ERH_RETURN); // << LOCKS m_GlobControlLock! if (s) { HLOGC(gslog.Debug, diff --git a/srtcore/packetfilter.cpp b/srtcore/packetfilter.cpp index e8b2a6102..51a045fa3 100644 --- a/srtcore/packetfilter.cpp +++ b/srtcore/packetfilter.cpp @@ -27,6 +27,11 @@ using namespace srt_logging; using namespace srt::sync; namespace srt { + +/////////////////////////////////////// +// CONFIGURATION section +////////////////////////////////////// + bool PacketFilter::Internal::ParseConfig(const string& s, SrtFilterConfig& w_config, PacketFilter::Factory** ppf) { if (!SrtParseConfig(s, (w_config))) @@ -112,6 +117,10 @@ bool PacketFilter::Internal::CheckFilterCompat(SrtFilterConfig& w_agent, const S return true; } +/////////////////////////////////////// +// RECEIVER section +////////////////////////////////////// + struct SortBySequence { bool operator()(const CUnit* u1, const CUnit* u2) @@ -206,38 +215,6 @@ void PacketFilter::receive(CUnit* unit, std::vector& w_incoming, loss_se } -bool PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_packet) -{ - bool have = m_filter->packControlPacket(m_sndctlpkt, seq); - if (!have) - return false; - - // Now this should be repacked back to CPacket. - // The header must be copied, it's always part of CPacket. - uint32_t* hdr = w_packet.getHeader(); - memcpy((hdr), m_sndctlpkt.hdr, SRT_PH_E_SIZE * sizeof(*hdr)); - - // The buffer can be assigned. - w_packet.m_pcData = m_sndctlpkt.buffer; - w_packet.setLength(m_sndctlpkt.length); - - // This sets only the Packet Boundary flags, while all other things: - // - Order - // - Rexmit - // - Crypto - // - Message Number - // will be set to 0/false - w_packet.set_msgflags(SRT_MSGNO_CONTROL | MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO)); - - // ... and then fix only the Crypto flags - w_packet.setMsgCryptoFlags(EncryptionKeySpec(kflg)); - - // Don't set the ID, it will be later set for any kind of packet. - // Write the timestamp clip into the timestamp field. - return true; -} - - void PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq) { if (m_provided.empty()) @@ -273,6 +250,114 @@ void PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq) m_provided.clear(); } + +/////////////////////////////////////// +// SENDER section +////////////////////////////////////// + +size_t PacketFilter::cacheControlPackets(int32_t seq) +{ + size_t oldsize = m_control.size(); + for (int y = 0; y < 16; ++y) + { + m_control.push_back(SrtPacket()); + size_t lastx = m_control.size() - 1; + bool have = m_filter->packControlPacket((m_control[lastx]), seq); + if (!have) + { + // Remove the prepared object if that time it didn't work. + m_control.pop_back(); + break; + } + } + + return m_control.size() - oldsize; +} + +void PacketFilter::copyPacket(SrtPacket& src, int kflg, CPacket& w_packet) +{ + // Now this should be repacked back to CPacket. + // The header must be copied, it's always part of CPacket. + uint32_t* hdr = w_packet.getHeader(); + memcpy((hdr), src.hdr, SRT_PH_E_SIZE * sizeof(*hdr)); + + // The buffer can be assigned. + w_packet.m_pcData = src.buffer; + w_packet.setLength(src.length); + + // This sets only the Packet Boundary flags, while all other things: + // - Order + // - Rexmit + // - Crypto + // - Message Number + // will be set to 0/false + w_packet.set_msgflags(SRT_MSGNO_CONTROL | MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO)); + + // ... and then fix only the Crypto flags + w_packet.setMsgCryptoFlags(EncryptionKeySpec(kflg)); + + // Don't set the ID, it will be later set for any kind of packet. + // Write the timestamp clip into the timestamp field. +} + +bool PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_packet) +{ + ScopedLock lk (m_SenderLock); + // First, check how many have been already once extracted. + // Note that extracted packages remain here until they are decommissioned. + + if (m_control_extracted == m_control.size()) // includes empty + { + // Nothing in cache, try to cache. + // Here we extract as much control packets as possible. + // All packets are being cached. One packet gets extracted here. + if (cacheControlPackets(seq) == 0) + return false; // still nothing in cache + } + + copyPacket(m_control[m_control_extracted], kflg, (w_packet)); + ++m_control_extracted; + return true; +} + +size_t PacketFilter::cachedPackets() const +{ + ScopedLock lk (m_SenderLock); + return m_control.size() - m_control_extracted; +} + +void PacketFilter::decommissionSender(int32_t seq) +{ + ScopedLock lk (m_SenderLock); + + vector::iterator i = m_control.begin(); + while (i != m_control.end()) + { + if (CSeqNo::seqcmp(i->header(SRT_PH_SEQNO), seq) > 0) + break; + } + // Now i points to a packet that should stay. + if (i == m_control.end()) + { + m_control.clear(); + m_control_extracted = 0; + } + else + { + int n = std::distance(m_control.begin(), i); + m_control.erase(m_control.begin(), i); + if (n >= int(m_control_extracted)) + m_control_extracted = 0; + else + m_control_extracted -= n; + } +} + + +/////////////////////////////////////// +// FACTORY section +////////////////////////////////////// + // Placement here is necessary in order to mark the location to // store the PacketFilter::Factory class characteristic object. PacketFilter::Factory::~Factory() diff --git a/srtcore/packetfilter.h b/srtcore/packetfilter.h index 2d1105042..da8a49005 100644 --- a/srtcore/packetfilter.h +++ b/srtcore/packetfilter.h @@ -179,13 +179,17 @@ class PacketFilter // In the beginning it's initialized as first, builtin default. // Still, it will be created only when requested. - PacketFilter(): m_filter(), m_parent(), m_sndctlpkt(0), m_unitq() {} + PacketFilter(): m_filter(), m_parent(), m_control_extracted(0), m_unitq() + { + } // Copy constructor - important when listener-spawning // Things being done: // 1. The filter is individual, so don't copy it. Set NULL. // 2. This will be configued anyway basing on possibly a new rule set. - PacketFilter(const PacketFilter& source SRT_ATR_UNUSED): m_filter(), m_parent(), m_sndctlpkt(0), m_unitq() {} + PacketFilter(const PacketFilter& source SRT_ATR_UNUSED): m_filter(), m_parent(), m_control_extracted(0), m_unitq() + { + } // This function will be called by the parent CUDT // in appropriate time. It should select appropriate @@ -203,7 +207,19 @@ class PacketFilter // Simple wrappers void feedSource(CPacket& w_packet) { SRT_ASSERT(m_filter); return m_filter->feedSource((w_packet)); } SRT_ARQLevel arqLevel() { SRT_ASSERT(m_filter); return m_filter->arqLevel(); } + + // Packs a single control packet. Prepares the cache, if needed. Returns false + // if no packet was cached and cache refreshing also failed. bool packControlPacket(int32_t seq, int kflg, CPacket& w_packet); + + // Caches any pending control packets and returns the number of + // packets that were not scheduled yet. + size_t cacheControlPackets(int32_t seq); + + // Removes from the control packet cache all packets older than + // the given sequence. + void decommissionSender(int32_t seq); + size_t cachedPackets() const; void receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs); protected: @@ -213,11 +229,19 @@ class PacketFilter CUDT* m_parent; // Sender part - SrtPacket m_sndctlpkt; + // After a single packet getting scheduled, there can be generated + // maximum of 2 control packets, if it happened after the last packet + // in the column and a complete row simultaneously + + mutable sync::Mutex m_SenderLock; + std::vector m_control; + size_t m_control_extracted; // Receiver part CUnitQueue* m_unitq; std::vector m_provided; + + static void copyPacket(SrtPacket& src, int kflg, CPacket& w_packet); }; inline bool CheckFilterCompat(SrtFilterConfig& w_agent, const SrtFilterConfig& peer) diff --git a/srtcore/packetfilter_api.h b/srtcore/packetfilter_api.h index ef0d8867f..d5058f1a6 100644 --- a/srtcore/packetfilter_api.h +++ b/srtcore/packetfilter_api.h @@ -69,7 +69,7 @@ struct SrtPacket char buffer[SRT_MAX_PLSIZE_AF_INET]; // Using this as the bigger one (this for AF_INET6 is smaller) size_t length; - SrtPacket(size_t size): length(size) + SrtPacket(size_t size = 0): length(size) { memset(hdr, 0, sizeof(hdr)); } diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 86c51ac86..55f819406 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -643,7 +643,7 @@ void CSndQueue::worker() << UST(Opened)); #undef UST - CUDTUnited::SocketKeeper sk (CUDT::uglobal(), u->id()); + SocketKeeper sk = CUDT::keep(u->id(), ERH_RETURN); if (!sk.socket) { HLOGC(qslog.Debug, log << "Socket to be processed was deleted in the meantime, not packing"); @@ -690,6 +690,83 @@ void CSndQueue::worker() THREAD_EXIT(); } +SendTask::taskiter_t CMultiplexer::scheduleSend(CUDTSocket* src, int32_t seqno, sched::Type type, const sync::steady_clock::time_point& when) +{ + SendTask task (SchedPacket(src, seqno, type), when); + return m_SndQueue.m_Scheduler.enqueue_task(src->id(), task); +} + +void CSndQueue::sched_worker() +{ + std::string thname; + ThreadName::get(thname); + THREAD_STATE_INIT(thname.c_str()); + + int interrupt_credit = 5; + + for (;;) + { + if (m_bClosing) + { + HLOGC(qslog.Debug, log << "SndQ: closed, exiting"); + break; + } + + HLOGC(qslog.Debug, log << "SndQ: waiting to get next send candidate..."); + THREAD_PAUSED(); + SchedPacket p = m_Scheduler.wait_pop(); + THREAD_RESUMED(); + INCREMENT_THREAD_ITERATIONS(); + + if (p.empty()) + { + --interrupt_credit; + if (!m_bClosing) + { + LOGC(qslog.Error, log << "SndQ: IPE: scheduler interrupted while queue is running!"); + if (!interrupt_credit) + { + m_bClosing = true; + LOGC(qslog.Fatal, log << "SndQ: IPE: closing sender queue to prevent spamming"); + break; + } + } + continue; // If m_bClosing, the loop will exit at the next iteration + } + + CPacket pkt; + CNetworkInterface source_addr; + CUDTSocket* s = p.m_Socket.socket; + const bool res = s->core().packData(p, (pkt), (source_addr)); + if (!res) + { + LOGC(qslog.Error, log << "SndQ: IPE: @" << s->id() + << " didn't provide packet for scheduled specification"); + --interrupt_credit; + if (!interrupt_credit) + break; + } + const sockaddr_any target_addr = s->core().m_PeerAddr; + m_pChannel->sendto(target_addr, pkt, source_addr); + + // Restore to maximum after a successful extraction. + interrupt_credit = 5; + + steady_clock::time_point when = s->core().calculateRegularSchedTime(); + + // Schedule the next packet, if possible. + // If not possible, the next packet will be chained directly + // by the API function. + CLastSched& sc = s->core().m_LastSched; + if (sc.chainSchedule(pkt.getSeqNo(), when)) + { + SendTask task (SchedPacket(s, sc.lastSchedSeq() , sched::TP_REGULAR), when); + m_Scheduler.enqueue_task(s->id(), task); + } + } + THREAD_EXIT(); +} + //* CRcvUList::CRcvUList() : m_pUList(NULL) @@ -1039,7 +1116,7 @@ void CRcvQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst, CUnit* uni EReadStatus read_st = rst; EConnectStatus conn_st = cst; - CUDTUnited::SocketKeeper sk (CUDT::uglobal(), i->id); + SocketKeeper sk = CUDT::keep(i->id, ERH_RETURN); if (!sk.socket) { // Socket deleted already, so stop this and proceed to the next loop. @@ -1098,7 +1175,7 @@ void CRcvQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst, CUnit* uni // Likely not necessary here - already removed by expiring and then in qualifyToHandleRID(). // m_parent->removeRID(i->id); - CUDTUnited::SocketKeeper sk (CUDT::uglobal(), i->id); + SocketKeeper sk = CUDT::keep(i->id, ERH_RETURN); if (!sk.socket) { // This actually shall never happen, so it's a kind of paranoid check. @@ -1662,7 +1739,7 @@ bool CRcvQueue::worker_TryAcceptedSocket(CUnit* unit, const sockaddr_any& addr) << " and address: " << addr.str() << " - POSSIBLE ATTACK, rejecting"); return false; } - CUDTUnited::SocketKeeper sk (CUDT::uglobal(), s); + SocketKeeper sk (CUDT::uglobal(), s); if (!sk.socket) return false; @@ -1701,7 +1778,7 @@ EConnectStatus CRcvQueue::worker_ProcessAddressedPacket(SRTSOCKET id, CUnit* uni // it will not be deleted for at least one GC cycle. But we still need // to maintain the object existence as long as it's in use. // Note that here we are out of any locks, so m_GlobControlLock can be locked. - CUDTUnited::SocketKeeper sk (CUDT::uglobal(), s); + SocketKeeper sk (CUDT::uglobal(), s); // Sanity check - should never happen, but better safe than sorry. if (!sk.socket) { diff --git a/srtcore/queue.h b/srtcore/queue.h index b3f7ecbbc..a5eaf13ee 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -56,6 +56,7 @@ modified by #include "common.h" #include "packet.h" #include "socketconfig.h" +#include "schedule_snd.h" #include "netinet_any.h" #include "utilities.h" #include @@ -410,6 +411,7 @@ class CSndQueue } void worker(); + void sched_worker(); sync::CThread m_WorkerThread; private: @@ -419,6 +421,8 @@ class CSndQueue sync::atomic m_bClosing; // closing the worker + SendScheduler m_Scheduler; + public: void tick() { return m_Timer.tick(); } @@ -767,6 +771,10 @@ struct CMultiplexer { m_SndQueue.m_pSndUList->remove(u); } + + // SCHEDULER API + + SendTask::taskiter_t scheduleSend(CUDTSocket* src, int32_t seqno, sched::Type type, const sync::steady_clock::time_point& when); }; } // namespace srt diff --git a/srtcore/socketconfig.cpp b/srtcore/socketconfig.cpp index b4909495c..d49a9f68a 100644 --- a/srtcore/socketconfig.cpp +++ b/srtcore/socketconfig.cpp @@ -956,6 +956,24 @@ struct CSrtConfigSetter }; #endif +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int val = cast_optval(optval, optlen); + + if (val < 0 || val > 1) + { + using namespace srt_logging; + LOGC(aclog.Error, log << "OPTION: sendmode: only 0 and 1 allowed"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + co.uSenderMode = val; + } +}; + int dispatchSet(SRT_SOCKOPT optName, CSrtConfig& co, const void* optval, int optlen) { switch (optName) @@ -1016,6 +1034,7 @@ int dispatchSet(SRT_SOCKOPT optName, CSrtConfig& co, const void* optval, int opt #ifdef ENABLE_MAXREXMITBW DISPATCH(SRTO_MAXREXMITBW); #endif + DISPATCH(SRTO_SENDMODE); #undef DISPATCH default: diff --git a/srtcore/socketconfig.h b/srtcore/socketconfig.h index 1d4666489..cc34fe530 100644 --- a/srtcore/socketconfig.h +++ b/srtcore/socketconfig.h @@ -281,6 +281,8 @@ struct CSrtConfig: CSrtMuxerConfig StringStorage sPacketFilterConfig; StringStorage sStreamName; + uint32_t uSenderMode; + // Shortcuts and utilities int32_t flightCapacity() { @@ -326,6 +328,7 @@ struct CSrtConfig: CSrtMuxerConfig , uKmPreAnnouncePkt(0) , uSrtVersion(SRT_DEF_VERSION) , uMinimumPeerSrtVersion(SRT_VERSION_MAJ1) + , uSenderMode(0) { // Default UDT configurations diff --git a/srtcore/srt.h b/srtcore/srt.h index 7eb161f5b..d4fbc168b 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -260,6 +260,7 @@ typedef enum SRT_SOCKOPT { SRTO_RETRANSMITALGO = 61, // An option to select packet retransmission algorithm SRTO_CRYPTOMODE = 62, // Encryption cipher mode (AES-CTR, AES-GCM, ...). SRTO_MAXREXMITBW = 63, // Maximum bandwidth limit for retransmision (Bytes/s) + SRTO_SENDMODE = 64, // Use of the sending mode SRTO_E_SIZE // Always last element, not a valid option. } SRT_SOCKOPT; diff --git a/srtcore/utilities.h b/srtcore/utilities.h index e3a57b350..a3902a903 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -487,6 +487,264 @@ class FixedArray T* const m_entries; }; +// NOTE: ALL logging instructions are commented-out here. +// They were used for debugging and can be also restored, +// but this header file should not include logging, hence +// this isn't implemented. +template +class HeapSet +{ + std::vector m_HeapArray; +public: + + static const size_t npos = std::string::npos; + + // Constructor + HeapSet(size_t capa = 0) + { + if (capa) + m_HeapArray.reserve(capa); + } + + const std::vector& raw() const { return m_HeapArray; } + + bool empty() const { return m_HeapArray.empty(); } + bool size() const { return m_HeapArray.size(); } + const NodeType operator[](size_t ix) const + { + return m_HeapArray[ix]; + } + + static size_t parent(size_t i) { return (i-1)/2; } + + // to get index of left child of node at index i + static size_t left(size_t i) { return (2*i + 1); } + + // to get index of right child of node at index i + static size_t right(size_t i) { return (2*i + 2); } + +private: + + NodeType pop_last() + { + NodeType out = m_HeapArray[m_HeapArray.size()-1]; + //LOG("POP-LAST: reheap after removal of: ", Access::print(out)); + m_HeapArray.pop_back(); + Access::position(out) = npos; + return out; + } + + NodeType pop_one() + { + NodeType nod = m_HeapArray[0]; + Access::position(nod) = npos; + m_HeapArray.clear(); + return nod; + } + +public: + + // to extract the root which is the minimum element + NodeType pop() + { + size_t s = m_HeapArray.size(); + if (s == 0) + { + //LOG("POP: empty"); + return Access::none(); + } + if (s == 1) + { + //LOG("POP: one"); + return pop_one(); + } + + //LOG("POP: SWAP [0]", Access::print(m_HeapArray[0]), " <-> [", (s-1), "]", Access::print(m_HeapArray[s-1]) ); + + std::swap(m_HeapArray[0], m_HeapArray[s-1]); + Access::position(m_HeapArray[0]) = 0; + + NodeType last = pop_last(); + reheap(0); + return last; + } + + // Decreases key value of key at index i to new_val + //void decreaseKey(int i, int new_val); + + // Returns the minimum key (key at root) from min heap + NodeType top() { return m_HeapArray[0]; } + + // Inserts a new key 'k' + size_t insert(NodeType node) + { + // First insert the new key at the end + Access::position(node) = m_HeapArray.size(); + m_HeapArray.push_back(node); + + // LOG("INSERT: ", Access::print(node), " initial position: ", Access::position(node) ); + + // Fix the min heap property if it is violated + for (size_t i = m_HeapArray.size() - 1; i != 0; i = parent(i)) + { + // LOG("INSERT: CHECK ORDER: [", i, "]", Access::print(m_HeapArray[i]), " < [", parent(i), "]", Access::print(m_HeapArray[parent(i)]) ); + if (Access::order(Access::key(m_HeapArray[i]), Access::key(m_HeapArray[parent(i)]))) + { + // LOG("INSERT: SWAP ", Access::print(m_HeapArray[i]), " <-> ", Access::print(m_HeapArray[parent(i)]) ); + std::swap(m_HeapArray[i], m_HeapArray[parent(i)]); + // After swapping restore their original positions + Access::position(m_HeapArray[i]) = i; + Access::position(m_HeapArray[parent(i)]) = parent(i); + } + else + break; + } + return Access::position(node); + } + + void erase(NodeType node) + { + // Assume the node is in the hash; make sure about the position first. + size_t pos = Access::position(node); + if (pos == npos) + return; + + //assert(pos < m_HeapArray.size() && m_HeapArray[pos] == node); + + size_t lastx = m_HeapArray.size() - 1; + if (lastx == 0) + { + // LOG("ERASE: one element, clearing"); + // One and the only element; enough to clear the container. + Access::position(node) = npos; + m_HeapArray.clear(); + return; + } + + // If position is the last element in the array, there's + // nothing to swap anyway. + if (pos != lastx) + { + // LOG("ERASE: SWAP ", Access::print(m_HeapArray[pos]), " <-> ", Access::print(m_HeapArray[lastx]) ); + std::swap(m_HeapArray[pos], m_HeapArray[lastx]); + Access::position(m_HeapArray[pos]) = pos; + } + + pop_last(); + reheap(0); + if (pos != lastx) + { + reheap(pos); + } + } + + // to heapify a subtree with the root at given index + void reheap(size_t i) + { + size_t l = left(i); + size_t r = right(i); + size_t earliest = i; + +#if ENABLE_LOGGING + std::string which = "parent"; + // LOGN("REHEAP: [", i, "]", Access::print(m_HeapArray[i]), " -> "); + if (l < m_HeapArray.size()) + { + // LOGN("[", l, "]", Access::print(m_HeapArray[l])); + if (r < m_HeapArray.size()) + { + // LOGN(" , [", r, "]", Access::print(m_HeapArray[r])); + } + else + { + // LOGN("[", r, "] (OVER ", m_HeapArray.size(), ")"); + } + } + else + { + // LOGN("[", l, "] (OVER ", m_HeapArray.size(), ")"); + } + // LOG(); +#endif + + if (l < m_HeapArray.size() && Access::order(Access::key(m_HeapArray[l]), Access::key(m_HeapArray[i]))) + { + earliest = l; + // IF_LOGGING(which = "left"); + } + if (r < m_HeapArray.size() && Access::order(Access::key(m_HeapArray[r]), Access::key(m_HeapArray[earliest]))) + { + earliest = r; + // IF_LOGGING(which = "right"); + } + // LOG("REHEAP: EARLIEST: ", which, ": -> [", earliest, "]", Access::print(m_HeapArray[earliest]) ); + + if (earliest != i) + { + // LOG("REHEAP: SWAP ", Access::print(m_HeapArray[i]), " <-> ", Access::print(m_HeapArray[earliest]), " CONTINUE FROM [", earliest, "]"); + std::swap(m_HeapArray[i], m_HeapArray[earliest]); + Access::position(m_HeapArray[i]) = i; + Access::position(m_HeapArray[earliest]) = earliest; + reheap(earliest); + } + else + { + // LOG("REHEAP: parent earlier than children, exitting procedure"); + } + } + + // Change the key value and let the element flow through + template + void update(NodeType node, const KeyType& newkey) + { + size_t pos = Access::position(node); + return update(pos, newkey); + } + + template + void update(size_t pos, const KeyType& newkey) + { + NodeType node = m_HeapArray[pos]; + const KeyType& oldkey = Access::key(node); + Access::key(node) = newkey; + + // LOG("UPDATE: rewind from [", pos, "]:"); + for (size_t i = pos; i != 0; i = parent(i)) + { + if (Access::order(Access::key(m_HeapArray[i]), Access::key(m_HeapArray[parent(i)]))) + { + // LOG("UPDATE: SWAP ", Access::print(m_HeapArray[i]), " <-> ", Access::print(m_HeapArray[parent(i)]), " CONTINUE FROM [", parent(i), "]"); + std::swap(m_HeapArray[i], m_HeapArray[parent(i)]); + Access::position(m_HeapArray[i]) = i; + Access::position(m_HeapArray[parent(i)]) = parent(i); + } + else + break; + } + } + + // Note: Access::print is optional, as long as you don't use this function. + void print_tree(std::ostream& out, size_t from = 0, int tabs = 0) const + { + for (size_t t = 0; t < tabs; ++t) + out << " "; + out << "[" << from << "]"; + if (from != Access::position(m_HeapArray[from])) + out << "!POS=" << Access::position(m_HeapArray[from]) << "!"; + out << "=" << Access::print(m_HeapArray[from]) << std::endl; + size_t l = left(from), r = right(from); + size_t size = m_HeapArray.size(); + + if (l < size) + { + print_tree(out, l, tabs + 1); + if (r < size) + print_tree(out, r, tabs + 1); + } + } + +}; + } // namespace srt // ------------------------------------------------------------ From 371606f83df368b3c0e7f60bdc0f7e503f0555b0 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Wed, 9 Jul 2025 08:57:33 +0200 Subject: [PATCH 02/26] Added SocketKeeper tracking. Added speed stats recording --- srtcore/api.cpp | 6 +++--- srtcore/common.h | 21 +++++++++++++++++++-- srtcore/core.cpp | 15 ++++++++++++--- srtcore/core.h | 28 +++++++++++++++++++++++++--- 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 4e7fa3941..a6935c31c 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -1125,7 +1125,7 @@ SRTSTATUS CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock) return SRT_STATUS_OK; } -void srt::CUDTUnited::bindSocketToMuxer(CUDTSocket* s, const sockaddr_any& address, UDPSOCKET* psocket) +void CUDTUnited::bindSocketToMuxer(CUDTSocket* s, const sockaddr_any& address, UDPSOCKET* psocket) { if (address.hport() == 0 && s->core().m_config.bRendezvous) throw CUDTException(MJ_NOTSUP, MN_ISRENDUNBOUND, 0); @@ -2214,7 +2214,7 @@ SRTSTATUS CUDTUnited::close(const SRTSOCKET u, int reason) }; #endif - SocketKeeper k = CUDT::keep(u, ERH_THROW); + SocketKeeper k = SOCKET_KEEP(u, ERH_THROW); IF_HEAVY_LOGGING(ScopedExitLog slog(k.socket)); HLOGC(smlog.Debug, log << "CUDTUnited::close/begin: @" << u << " busy=" << k.socket->isStillBusy()); @@ -2351,7 +2351,7 @@ void CUDTSocket::breakNonAcceptedSockets() HLOGC(smlog.Debug, log << "breakNonAcceptedSockets: found " << accepted.size() << " leaky accepted sockets"); for (vector::iterator i = accepted.begin(); i != accepted.end(); ++i) { - SocketKeeper sk = CUDT::keep(*i, ERH_RETURN); + SocketKeeper sk = SOCKET_KEEP(*i, ERH_RETURN); if (sk.socket) { sk.socket->m_UDT.m_bBroken = true; diff --git a/srtcore/common.h b/srtcore/common.h index a892c2f8c..223156c74 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -138,12 +138,28 @@ struct CNetworkInterface } }; +#if ENABLE_HEAVY_LOGGING +inline std::string RecordLocation(const char* file, int line) +{ + std::ostringstream out; + out << file << ":" << line; + return out.str(); +} +#else +inline std::string RecordLocation(const char*, int) { return std::string(); } +#endif + struct SocketKeeper { CUDTSocket* socket; CUDTUnited& glob; + std::string location; - SocketKeeper(CUDTUnited& go, CUDTSocket* p = NULL): socket(p), glob(go) {} + SocketKeeper(CUDTUnited& go, CUDTSocket* p = NULL, bool acquire_after = true): socket(p), glob(go) + { + if (acquire_after && socket) + acquire_socket(socket); + } SocketKeeper(const SocketKeeper& r): socket(r.socket), glob(r.glob) { @@ -160,7 +176,8 @@ struct SocketKeeper ~SocketKeeper() { - release_socket(socket); + if (socket) + release_socket(socket); } SRTSOCKET id() const; diff --git a/srtcore/core.cpp b/srtcore/core.cpp index eb7065416..cebdca643 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -274,21 +274,27 @@ srt::SocketKeeper srt::CUDT::keep_noacquire(srt::CUDTSocket* s) return k; } -srt::SocketKeeper srt::CUDT::keep(srt::CUDTSocket* s) +srt::SocketKeeper srt::CUDT::keep(srt::CUDTSocket* s, string loc) { SocketKeeper k(uglobal()); if (s == NULL || !uglobal().acquireSocket(s)) { + HLOGC(gglog.Debug, log << "Socket " << s << " acquisition failed at " << loc); return k; } k.socket = s; + HLOGC(gglog.Debug, log << "Socket " << s << " @" << s->id() << " acquisition at " << loc); + k.location = loc; return k; } -srt::SocketKeeper srt::CUDT::keep(SRTSOCKET id, ErrorHandling erh) +srt::SocketKeeper srt::CUDT::keep(SRTSOCKET id, ErrorHandling erh, string loc) { - return SocketKeeper (uglobal(), uglobal().locateAcquireSocket(id, erh)); + HLOGC(gglog.Debug, log << "Socket @" << id << " acquisition at " << loc); + SocketKeeper kp (uglobal(), uglobal().locateAcquireSocket(id, erh), false /* do not acquire again*/); + kp.location = loc; + return kp; } void srt::SocketKeeper::acquire_socket(CUDTSocket* s) @@ -6321,7 +6327,10 @@ bool srt::CUDT::closeEntity(int reason) ATR_NOEXCEPT // remove this socket from the snd queue if (m_bConnected) + { + HLOGC(smlog.Debug, log << CONID() << "CLOSING: Remove from sender queue"); m_pMuxer->removeSender(this); + } /* * update_events below useless diff --git a/srtcore/core.h b/srtcore/core.h index 997893207..a184607e9 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -176,12 +176,30 @@ class CLastSend { sync::steady_clock::time_point old = m_tsBeginTime; m_tsBeginTime.store(m_tsSendTime.load()); + + if (!is_zero(old)) + { + sync::steady_clock::duration diff = m_tsBeginTime.load() - old; + uint64_t basesize = m_uNumberBytes * 1000 * 1000; + uint64_t basetime = count_microseconds(diff); + if (basetime) // prevent division by 0 + m_uBytesPerSecond = basesize / basetime; + // Otherwise keep unchanged; this branch is considered to + // be run only if there was some data collected b4 + } + else + { + m_uBytesPerSecond = 0; + } m_tsSendTime = tm; m_uNumberBytes = 4; m_uNumberPackets = 1; sync::steady_clock::duration diff = tm - m_tsBeginTime; - m_uBytesPerSecond = (m_uNumberBytes * 1000000) / count_milliseconds(diff); + uint64_t basesize = m_uNumberBytes * 1000 * 1000; + uint64_t basetime = count_microseconds(diff); + if (basetime) // prevent division by 0 + m_uBytesPerSecond = (5*m_uBytesPerSecond + (basesize / basetime))/6; } void update(const sync::steady_clock::time_point& tm, uint32_t bytes, uint32_t npackets = 1) @@ -190,6 +208,8 @@ class CLastSend // XXX IMPLEMENT R-M-W mode += operator for atomics!!! m_uNumberBytes = m_uNumberBytes + bytes; m_uNumberPackets = m_uNumberPackets + npackets; + + // Do not calculate speed here. Do it on reset only. } }; @@ -663,9 +683,11 @@ class CUDT static CUDTUnited& uglobal(); // UDT global management base - static SocketKeeper keep(CUDTSocket* s); + static SocketKeeper keep(CUDTSocket* s, std::string loc = ""); static SocketKeeper keep_noacquire(CUDTSocket* s); - static SocketKeeper keep(SRTSOCKET, ErrorHandling erh = ERH_THROW); + static SocketKeeper keep(SRTSOCKET, ErrorHandling erh = ERH_RETURN, std::string loc = ""); + +#define SOCKET_KEEP(...) CUDT::keep(__VA_ARGS__, RecordLocation(__FILE__, __LINE__)) std::set& pollset() { return m_sPollID; } From 6a9498528c0fe922a04304ec0c239d7f4fd382aa Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Fri, 11 Jul 2025 08:50:39 +0200 Subject: [PATCH 03/26] Added stop handling and muxer config entry. STILL NOT TESTED. --- srtcore/core.cpp | 41 +++++++++++-- srtcore/queue.cpp | 36 ++++++++--- srtcore/queue.h | 18 +++--- srtcore/schedule_snd.cpp | 127 ++++++++++++++++++++++++++++++--------- srtcore/schedule_snd.h | 33 +++++----- srtcore/socketconfig.h | 6 +- srtcore/utilities.h | 7 ++- 7 files changed, 198 insertions(+), 70 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index f2a1d1088..62b63307c 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -10761,11 +10761,7 @@ int srt::CUDT::processData(CUnit* in_unit) // We expect the 16th and 17th packet to be sent regularly, // otherwise measurement must be rejected. - // NOTE: probe 16 shall NOT be executed in case of scheduler mode. - if (m_config.uSenderMode == 0) - { - m_RcvTimeWindow.probeArrival(packet, unordered || retransmitted); - } + m_RcvTimeWindow.probeArrival(packet, unordered || retransmitted); enterCS(m_StatsLock); m_stats.rcvr.recvd.count(pktsz); @@ -12071,9 +12067,42 @@ bool srt::CUDT::defineSchedTimes(int32_t lo, int32_t hi, time_point& w_start, du //int distance = CSeqNo::seqlen(lo, hi); - // loss should get higher priority, so we time_point start = m_LastSend.time(); + // XXX HERE do some estimation on how much time you have to send packes + // depending on the expected arrival time and whether there's measurement + // time or flush time. + + // The application shall send packets in groups, while within a group all packets + // should be declared the same delivery time (as expected for the last packet) + // and all should be sent one after another without waiting in between. + + // This procedure should now define sending time for all packets in the range + // depending on the current mode: + // - In measurement mode, all packets should be scheduled as fast as possible. + // The scheduler should be configured here to send packets with maximum + // currently allowed speed, which is defined in m_tdSendInterval. That + // value should be shaped according to the MAXBW settings, also "infinite", + // as well as the currently measured "bandwidth" speed on the link (that is + // for measurement the speed may also increase in time). Note that the LAST + // packet in the group must be sent at time not earlier than RTT + LATENCY + EAT. + // - In flush time, usually set when difference frames are to be scheduled, + // the step time should be defined as: + // - for regular packets, the measured "reasonable" speed, which is the + // speed that should ensure no packet loss, at minimum the speed required + // to send all packets from the group up to the declared time (the + // whole timeslice split into the number of packets in the group). + // - for lost packets, keep the "bandwidh overhead" rule, that is, use the + // "maximum reasonable" speed (or MAXBW if lower) + overhead percentage. + + // Summary of the data required for calculations: + // + // - BANDWIDTH: the currently measured maximum speed. May exceed the MAXBW. + // - MAXBW, INPUTBW: declared or measured maximum bandwidth + // - OHEADBW: the overhead percentage, allowed for retransmissions + // - TOPBW: the maximum speed measured during measurement time + // + w_start = start; w_step = m_tdSendInterval; return true; diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 5e03848dc..720d6d861 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -524,6 +524,7 @@ void CSndUList::remove_(CSNode* n) // CSndQueue::CSndQueue(CMultiplexer* parent): m_parent(parent), + m_pWorkerFunction(NULL), m_pSndUList(NULL), m_pChannel(NULL), m_bClosing(false) @@ -532,14 +533,6 @@ CSndQueue::CSndQueue(CMultiplexer* parent): void CSndQueue::stopWorker() { - // We use the decent way, so we say to the thread "please exit". - m_bClosing = true; - - m_Timer.interrupt(); - - if (m_pSndUList) // Could have been never created - m_pSndUList->signalInterrupt(); - // Sanity check of the function's affinity. if (sync::this_thread::get_id() == m_WorkerThread.get_id()) { @@ -547,6 +540,21 @@ void CSndQueue::stopWorker() return; // do nothing else, this would cause a hangup or crash. } + // We use the decent way, so we say to the thread "please exit". + m_bClosing = true; + + if (m_pWorkerFunction == &worker_fwd) + { + m_Timer.interrupt(); + + if (m_pSndUList) // Could have been never created + m_pSndUList->signalInterrupt(); + } + else if (m_pWorkerFunction == &sched_worker_fwd) + { + m_Scheduler.interrupt(); + } + HLOGC(rslog.Debug, log << "SndQueue: EXIT (forced)"); // And we trust the thread that it does. if (m_WorkerThread.joinable()) @@ -577,12 +585,22 @@ void CSndQueue::init(CChannel* c) #else const char* thname = "SRT:SndQ"; #endif - if (!StartThread((m_WorkerThread), CSndQueue::worker_fwd, this, thname)) + m_pWorkerFunction = SelectWorkerFunction(); + + if (!StartThread((m_WorkerThread), *m_pWorkerFunction, this, thname)) { throw CUDTException(MJ_SYSTEMRES, MN_THREAD); } } +CSndQueue::worker_fn* CSndQueue::SelectWorkerFunction() +{ + if (m_parent->cfg().uSenderMode == 0) + return worker_fwd; + + return sched_worker_fwd; +} + #if defined(SRT_DEBUG_SNDQ_HIGHRATE) static void CSndQueueDebugHighratePrint(const CSndQueue* self, const steady_clock::time_point currtime) diff --git a/srtcore/queue.h b/srtcore/queue.h index 64be324bc..51d8e25bf 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -404,18 +404,20 @@ class CSndQueue void stopWorker(); private: - static void* worker_fwd(void* param) - { - CSndQueue* self = (CSndQueue*)param; - self->worker(); - return NULL; - } + typedef void* worker_fn(void*); +#define DEFINE_FWD(classname, name) \ + static void* name##_fwd(void* param) { classname* self = (classname*)param; self->name(); return NULL; } void worker(); + DEFINE_FWD(CSndQueue, worker); void sched_worker(); + DEFINE_FWD(CSndQueue, sched_worker); + +#undef DEFINE_FWD + + worker_fn* m_pWorkerFunction; sync::CThread m_WorkerThread; -private: CSndUList* m_pSndUList; // List of UDT instances for data sending CChannel* m_pChannel; // The UDP channel for data sending sync::CTimer m_Timer; // Timing facility @@ -424,6 +426,8 @@ class CSndQueue SendScheduler m_Scheduler; + worker_fn* SelectWorkerFunction(); + public: void tick() { return m_Timer.tick(); } diff --git a/srtcore/schedule_snd.cpp b/srtcore/schedule_snd.cpp index c2580ddc5..ee2a6dfea 100644 --- a/srtcore/schedule_snd.cpp +++ b/srtcore/schedule_snd.cpp @@ -4,11 +4,14 @@ #include "schedule_snd.h" #include "core.h" +#include "logging.h" using namespace std; using namespace srt; using namespace srt::sync; +using srt_logging::qslog; + namespace srt { @@ -61,31 +64,49 @@ std::list SendTask::free_list; SendTask::taskiter_t SendScheduler::enqueue_task(socket_t id, const SendTask& proto) { - if (broken) + if (m_bBroken) + { + HLOGC(qslog.Debug, log << "Schedule: ENQ: DENIED, schedule is broken"); return SendTask::none(); + } - sync::ScopedLock lk (lock); - SendTask::tasklist_t& wlist = taskmap[id]; + sync::ScopedLock lk (m_Lock); + SendTask::tasklist_t& wlist = m_TaskMap[id]; wlist.push_back(proto); SendTask::taskiter_t itask = --wlist.end(); itask->m_pBaseList = &wlist; bool was_ready = have_task_ready(); - // Now enqueue it in tq - size_t pos = tq.insert(itask); + // Now enqueue it in m_TaskQueue + size_t pos = m_TaskQueue.insert(itask); + + IF_HEAVY_LOGGING(bool was_first = false); if (pos == 0) // earliest task - about_time = tq.top()->m_tsSendTime; // INSERTED: will not be empty + { + m_tsAboutTime = m_TaskQueue.top()->m_tsSendTime; // INSERTED: will not be empty + IF_HEAVY_LOGGING(was_first = true); + } + if (!was_ready && have_task_ready()) - task_ready.notify_all(); + { + HLOGC(qslog.Debug, log << "Schedule: ENQ: new READY task at T=" << FormatTime(itask->m_tsSendTime) + << (was_first ? " (NEW TOP)" : "") << " - NOTIFY"); + m_TaskReadyCond.notify_all(); + } + else + { + HLOGC(qslog.Debug, log << "Schedule: ENQ: new task at T=" << FormatTime(itask->m_tsSendTime) + << (was_first ? " (NEW TOP)" : "") << (!was_ready ? " (NOT READY YET)" : " (?)")); + } return itask; } bool SendScheduler::have_task_ready() { - if (!tq.empty()) + if (!m_TaskQueue.empty()) { - SendTask::taskiter_t earliest = tq.top(); + SendTask::taskiter_t earliest = m_TaskQueue.top(); if (earliest->is_ready(clock_type::now())) { return true; @@ -96,56 +117,93 @@ bool SendScheduler::have_task_ready() bool SendScheduler::wait() { - UniqueLock lk (lock); + UniqueLock lk (m_Lock); + typedef steady_clock::time_point ClockTime; for (;;) { - if (broken) + if (m_bBroken) + { + HLOGC(qslog.Debug, log << "Schedule: WAIT: not waiting, schedule is broken"); return false; + } + + IF_HEAVY_LOGGING(ClockTime now = steady_clock::now()); if (have_task_ready()) + { + IF_HEAVY_LOGGING(ClockTime next = m_TaskQueue.top()->m_tsSendTime); + HLOGC(qslog.Debug, log << "Schedule: WAIT: task ready since " << FormatDurationAuto(now - next)); break; - task_ready.wait(lk); + } + +#if ENABLE_HEAVY_LOGGING + if (m_TaskQueue.empty()) + { + LOGC(qslog.Debug, log << "Schedule: WAIT: task NOT ready, NO NEW TASKS, WAIT FOR SIGNAL"); + } + else + { + ClockTime next = m_TaskQueue.top()->m_tsSendTime; + LOGC(qslog.Debug, log << "Schedule: WAIT: task not ready, next in " + << FormatDurationAuto(next - now) << " at T=" << FormatTime(next) << " - WAIT FOR READY"); + } +#endif + + m_TaskReadyCond.wait(lk); } return true; } void SendScheduler::withdraw(socket_t id) { - sync::ScopedLock lk (lock); + sync::ScopedLock lk (m_Lock); // Delete all tasks for the given socket id. - // We have them collected in the list: taskmap + // We have them collected in the list: m_TaskMap + + SendTask::tasklist_t& id_list = m_TaskMap[id]; - SendTask::tasklist_t& id_list = taskmap[id]; + // As we know that all these items were added to m_TaskQueue, + // we need to withdraw them all from m_TaskQueue. - // As we know that all these items were added to tq, - // we need to withdraw them all from tq. + IF_HEAVY_LOGGING(int nerased = 0); for (SendTask::taskiter_t idt = id_list.begin(); idt != id_list.end(); ++idt) { - tq.erase(idt); + if (m_TaskQueue.erase(idt)) + { + IF_HEAVY_LOGGING(++nerased); + } } // The list should be empty, so delete the entry itself. - taskmap.erase(id); + int iderased SRT_ATR_UNUSED = m_TaskMap.erase(id); // We don't know if the earliest in the queue was deleted, // so just rewrite it anyway. pop_update_time(); + + IF_HEAVY_LOGGING(string nextone = m_TaskQueue.empty() + ? string("NO NEXT TASK") + : "next in " + FormatDurationAuto(m_tsAboutTime - steady_clock::now()) + " from @" + Sprint(m_TaskQueue.top()->m_Packet.id())); + + HLOGC(qslog.Debug, log << "Schedule: withdrawn @" << int(id) + << (iderased ? "" : " (NOT FOUND!)") << " - erased " << nerased << " tasks -" << nextone); } void SendScheduler::pop_update_time() { - if (!tq.empty()) - about_time = tq.top()->m_tsSendTime; // checked that ! empty + if (!m_TaskQueue.empty()) + m_tsAboutTime = m_TaskQueue.top()->m_tsSendTime; // checked that ! empty else - about_time = clock_time(); + m_tsAboutTime = clock_time(); } void SendScheduler::cancel(SendTask::taskiter_t itask) { - sync::ScopedLock lk (lock); + sync::ScopedLock lk (m_Lock); cancel_nolock(itask); } void SendScheduler::cancel_nolock(SendTask::taskiter_t itask) { - tq.erase(itask); + HLOGC(qslog.Debug, log << "Schedule: CANCEL: @" << itask->m_Packet.id() << " T=" << FormatTime(itask->m_tsSendTime)); + m_TaskQueue.erase(itask); itask->m_pBaseList->erase(itask); pop_update_time(); } @@ -153,24 +211,36 @@ void SendScheduler::cancel_nolock(SendTask::taskiter_t itask) SchedPacket SendScheduler::wait_pop() { SchedPacket packet; - sync::ScopedLock lk (lock); + sync::ScopedLock lk (m_Lock); // Wait until the time has come to execute // the next task. Extract the task structure // and remove the task from the list. for (;;) { - if (broken) + if (m_bBroken) + { + HLOGC(qslog.Debug, log << "Schedule: wait_pop: broken"); break; + } bool have = wait(); if (have) + { break; + } + else + { + HLOGC(qslog.Debug, log << "Schedule: wait_pop: SPURIOUS"); + } } // Here we are sure that the top() task is ready to execute - SendTask::taskiter_t itask = tq.pop(); + SendTask::taskiter_t itask = m_TaskQueue.pop(); pop_update_time(); if (itask == SendTask::none()) + { + HLOGC(qslog.Debug, log << "Schedule: wait_pop: IPE: THE QUEUE IS EMPTY"); return SchedPacket(); + } // The node is already removed from the heapset. // Extract the required data @@ -179,6 +249,9 @@ SchedPacket SendScheduler::wait_pop() // Now remove it from the corresponding list. itask->m_pBaseList->erase(itask); + IF_HEAVY_LOGGING(static string typenames[3] = {"REGULAR", "REXMIT", "CONTROL"}); + HLOGC(qslog.Debug, log << "Schedule: wait_pop: PICKUP from @" << packet.id() << " %" << packet.seqno() << " type=" << typenames[packet.type()]); + return packet; } diff --git a/srtcore/schedule_snd.h b/srtcore/schedule_snd.h index bc67a7ebe..42b1da6d8 100644 --- a/srtcore/schedule_snd.h +++ b/srtcore/schedule_snd.h @@ -116,30 +116,32 @@ struct SendScheduler typedef clock_type::time_point clock_time; protected: - std::map taskmap; - HeapSet tq; - clock_time about_time; + std::map m_TaskMap; + HeapSet m_TaskQueue; + clock_time m_tsAboutTime; void pop_update_time(); - sync::Mutex lock; - sync::Condition task_ready; - sync::atomic broken; + sync::Mutex m_Lock; + sync::Condition m_TaskReadyCond; + sync::atomic m_bBroken; public: - const HeapSet& queue() { return tq; } + const HeapSet& queue() { return m_TaskQueue; } - SendScheduler(): broken(false) + SendScheduler(): m_bBroken(false) { } void interrupt() { - broken = true; + m_bBroken = true; + sync::ScopedLock hold (m_Lock); + m_TaskReadyCond.notify_all(); // Force waiting functions to exit } bool running() { - return !broken; + return !m_bBroken; } SendTask::taskiter_t enqueue_task(socket_t id, const SendTask& proto); @@ -147,6 +149,7 @@ struct SendScheduler void update_task(SendTask::taskiter_t ti); protected: + // This is NOLOCK; derived classes please use lock. bool have_task_ready(); public: @@ -158,14 +161,14 @@ struct SendScheduler template void withdraw_if(socket_t id, Predicate match) { - sync::ScopedLock lk (lock); + sync::ScopedLock lk (m_Lock); // Delete all tasks for the given socket id. - // We have them collected in the list: taskmap + // We have them collected in the list: m_TaskMap - SendTask::tasklist_t& id_list = taskmap[id]; + SendTask::tasklist_t& id_list = m_TaskMap[id]; - // As we know that all these items were added to tq, - // we need to withdraw them all from tq. + // As we know that all these items were added to m_TaskQueue, + // we need to withdraw them all from m_TaskQueue. SendTask::taskiter_t idt_next = id_list.begin(); for (SendTask::taskiter_t idt = idt_next; idt != id_list.end(); idt = idt_next) { diff --git a/srtcore/socketconfig.h b/srtcore/socketconfig.h index cc34fe530..25cd9b607 100644 --- a/srtcore/socketconfig.h +++ b/srtcore/socketconfig.h @@ -87,6 +87,7 @@ struct CSrtMuxerConfig int iIpToS; int iIpV6Only; // IPV6_V6ONLY option (-1 if not set) bool bReuseAddr; // reuse an exiting port or not, for UDP multiplexer + uint32_t uSenderMode; #ifdef SRT_ENABLE_BINDTODEVICE std::string sBindToDevice; @@ -102,6 +103,7 @@ struct CSrtMuxerConfig return CEQUAL(iIpTTL) && CEQUAL(iIpToS) && CEQUAL(bReuseAddr) + && CEQUAL(uSenderMode) #ifdef SRT_ENABLE_BINDTODEVICE && CEQUAL(sBindToDevice) #endif @@ -122,6 +124,7 @@ struct CSrtMuxerConfig , iIpToS(-1) /* IPv4 Type of Service or IPv6 Traffic Class [0x00..0xff] (-1:undefined) */ , iIpV6Only(-1) , bReuseAddr(true) // This is default in SRT + , uSenderMode(0) , iUDPSndBufSize(DEF_UDP_BUFFER_SIZE) , iUDPRcvBufSize(DEF_UDP_BUFFER_SIZE) { @@ -281,8 +284,6 @@ struct CSrtConfig: CSrtMuxerConfig StringStorage sPacketFilterConfig; StringStorage sStreamName; - uint32_t uSenderMode; - // Shortcuts and utilities int32_t flightCapacity() { @@ -328,7 +329,6 @@ struct CSrtConfig: CSrtMuxerConfig , uKmPreAnnouncePkt(0) , uSrtVersion(SRT_DEF_VERSION) , uMinimumPeerSrtVersion(SRT_VERSION_MAJ1) - , uSenderMode(0) { // Default UDT configurations diff --git a/srtcore/utilities.h b/srtcore/utilities.h index a3902a903..45798e7ae 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -602,12 +602,12 @@ class HeapSet return Access::position(node); } - void erase(NodeType node) + bool erase(NodeType node) { // Assume the node is in the hash; make sure about the position first. size_t pos = Access::position(node); if (pos == npos) - return; + return false; //assert(pos < m_HeapArray.size() && m_HeapArray[pos] == node); @@ -618,7 +618,7 @@ class HeapSet // One and the only element; enough to clear the container. Access::position(node) = npos; m_HeapArray.clear(); - return; + return true; } // If position is the last element in the array, there's @@ -636,6 +636,7 @@ class HeapSet { reheap(pos); } + return true; } // to heapify a subtree with the root at given index From 5b4cb49a1cbb6fe41474edaa1bfc4aeb419cc385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 11 Jul 2025 12:04:41 +0200 Subject: [PATCH 04/26] Fixed bug: twice locking in wait. STILL TESTS FAIL --- srtcore/schedule_snd.cpp | 21 ++++++++++++++++++--- srtcore/schedule_snd.h | 8 ++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/srtcore/schedule_snd.cpp b/srtcore/schedule_snd.cpp index ee2a6dfea..b232e79cc 100644 --- a/srtcore/schedule_snd.cpp +++ b/srtcore/schedule_snd.cpp @@ -118,6 +118,11 @@ bool SendScheduler::have_task_ready() bool SendScheduler::wait() { UniqueLock lk (m_Lock); + return wait_extlock((lk)); +} + +bool SendScheduler::wait_extlock(UniqueLock& lk) +{ typedef steady_clock::time_point ClockTime; for (;;) { @@ -143,7 +148,7 @@ bool SendScheduler::wait() else { ClockTime next = m_TaskQueue.top()->m_tsSendTime; - LOGC(qslog.Debug, log << "Schedule: WAIT: task not ready, next in " + LOGC(qslog.Debug, log << "Schedule: WAIT: task NOT ready, next in " << FormatDurationAuto(next - now) << " at T=" << FormatTime(next) << " - WAIT FOR READY"); } #endif @@ -200,6 +205,16 @@ void SendScheduler::cancel(SendTask::taskiter_t itask) cancel_nolock(itask); } +void SendScheduler::interrupt() +{ + m_bBroken = true; + sync::ScopedLock hold (m_Lock); + HLOGC(qslog.Debug, log << "Schedule: INTERRUPT: notifying waiters"); + + m_TaskReadyCond.notify_all(); // Force waiting functions to exit +} + + void SendScheduler::cancel_nolock(SendTask::taskiter_t itask) { HLOGC(qslog.Debug, log << "Schedule: CANCEL: @" << itask->m_Packet.id() << " T=" << FormatTime(itask->m_tsSendTime)); @@ -211,7 +226,7 @@ void SendScheduler::cancel_nolock(SendTask::taskiter_t itask) SchedPacket SendScheduler::wait_pop() { SchedPacket packet; - sync::ScopedLock lk (m_Lock); + sync::UniqueLock lk (m_Lock); // Wait until the time has come to execute // the next task. Extract the task structure // and remove the task from the list. @@ -222,7 +237,7 @@ SchedPacket SendScheduler::wait_pop() HLOGC(qslog.Debug, log << "Schedule: wait_pop: broken"); break; } - bool have = wait(); + bool have = wait_extlock((lk)); if (have) { break; diff --git a/srtcore/schedule_snd.h b/srtcore/schedule_snd.h index 42b1da6d8..96bf095b9 100644 --- a/srtcore/schedule_snd.h +++ b/srtcore/schedule_snd.h @@ -132,12 +132,7 @@ struct SendScheduler { } - void interrupt() - { - m_bBroken = true; - sync::ScopedLock hold (m_Lock); - m_TaskReadyCond.notify_all(); // Force waiting functions to exit - } + void interrupt(); bool running() { @@ -155,6 +150,7 @@ struct SendScheduler public: // Wait until the time has come bool wait(); + bool wait_extlock(srt::sync::UniqueLock&); void withdraw(socket_t id); From c5b1e2c5539cdfa42e92a0f47abd8380c11cf8b4 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Wed, 13 Aug 2025 17:24:30 +0200 Subject: [PATCH 05/26] Added HeapSet::find_next function --- srtcore/schedule_snd.h | 14 ++-- srtcore/utilities.h | 154 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 6 deletions(-) diff --git a/srtcore/schedule_snd.h b/srtcore/schedule_snd.h index 96bf095b9..7130d8bf3 100644 --- a/srtcore/schedule_snd.h +++ b/srtcore/schedule_snd.h @@ -58,14 +58,16 @@ struct SendTask { typedef std::list tasklist_t; typedef typename tasklist_t::iterator taskiter_t; - typedef sync::steady_clock::time_point key_t; - key_t m_tsSendTime; + typedef sync::steady_clock::time_point key_type; + key_type m_tsSendTime; SchedPacket m_Packet; sync::atomic m_zHeapPos; // Required by HeapSet std::list* m_pBaseList; // Same definition as by HeapSet; here a shortcut. - static const size_t npos = HeapSet::npos; + // Can't use the definition from HeapSet because it's + // a template that has requirements for the type parameter. + static const size_t npos = std::string::npos; SendTask() : m_tsSendTime(), m_Packet(), m_zHeapPos(npos), m_pBaseList(0) {} @@ -83,7 +85,7 @@ struct SendTask m_pBaseList(src.m_pBaseList) {} - bool is_ready(key_t basetime) const + bool is_ready(key_type basetime) const { return m_tsSendTime < basetime; } @@ -97,8 +99,8 @@ struct SendTask } static sync::atomic& position(taskiter_t v) { return v->m_zHeapPos; } - static key_t& key(taskiter_t v) { return v->m_tsSendTime; } - static bool order(const key_t& left, const key_t& right) + static key_type& key(taskiter_t v) { return v->m_tsSendTime; } + static bool order(const key_type& left, const key_type& right) { return left < right; } diff --git a/srtcore/utilities.h b/srtcore/utilities.h index a09aa4f66..b26bc8a65 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -314,6 +314,12 @@ template class HeapSet { std::vector m_HeapArray; + + typename Access::key_type keyat(size_t position) const + { + return Access::key(m_HeapArray[position]); + } + public: static const size_t npos = std::string::npos; @@ -342,8 +348,156 @@ class HeapSet // to get index of right child of node at index i static size_t right(size_t i) { return (2*i + 2); } + NodeType find_next(typename Access::key_type limit) const + { + // This function should find the first node that is next in order + // towards the key value of 'limit'. + + // This is done by recursive search through the tree. The search + // goes deeper, when found an element that is still earlier than + // limit. When found elements in the path of both siblings, the + // earlier of these two is returned. There could be none found, + // and in this case none() is returned. + + if (m_HeapArray.empty()) + return Access::none(); + + // Check the very first candidate; if it's already later, you + // can return it. Otherwise check the children. + + if (!Access::order(keyat(0), limit)) + return m_HeapArray[0]; + + if (left(0) >= m_HeapArray.size()) + { + // There's no left, so there's no right either. + return Access::none(); + } + + // We have left, but not necessarily right. + size_t left_candidate = find_next_candidate(left(0), limit); + + size_t right_candidate = 0; + if (right(0) < m_HeapArray.size()) + right_candidate = find_next_candidate(right(0), limit); + + if (right_candidate == 0) + { + // Only left can be taken into account, so return + // whatever was found + if (left_candidate == 0) + return Access::none(); + return m_HeapArray[left_candidate]; + } + + if (left_candidate == 0 || Access::order(keyat(right_candidate), keyat(left_candidate))) + return m_HeapArray[right_candidate]; + + return m_HeapArray[left_candidate]; + } + private: + // This function, per given node, should find the element that is next in + // order towards 'limit', or return 0 if not found (0 can be used here as + // a trap value because the first 3 items are checked on a fast path). + size_t find_next_candidate(size_t position, typename Access::key_type limit) const + { + // It should be guaranteed before the call that position is still + // within the range of existing elements. + + // Ok, so first you check the element at position. If this element + // is already the next after limit, return it. + if (!Access::order(keyat(position), limit)) + return position; + + // Otherwise check the children and if both are next to it, select the + // earlier one in order. + + // If both children are prior to limit, call this function for both + // children and select tne next one. + + size_t left_pos = left(position), right_pos = right(position); + + // Directional 3-way value: + // -1 : no element here + // 0 : the element is earlier, so follow down + // 1 : the element is later, so it's a candidate + int left_check = -1, right_check = -1; + if (left_pos < m_HeapArray.size()) + { + // Exists, so add 0/1 that define the order condition + left_check = Access::order(limit, keyat(left_pos)); + } + + if (right_pos < m_HeapArray.size()) + { + right_check = Access::order(limit, keyat(right_pos)); + } + + // Ok, now start from the left one, then take the right one. + // If left doesn't exist, right wouldn't exist, too. + if (left_check == -1) + return 0; // no later found, so return none. + + // --- "ELIMINATE ZERO" phase + // This does it first for the left_check, but then right_check. + // For both, if they are 0, it is now turned into either 1 or -1. + + if (left_check == 0) + { + size_t deep_left = find_next_candidate(left_pos, limit); + if (deep_left == 0) + left_check = -1; + else + { + left_check = 1; + left_pos = deep_left; + } + } + + if (right_check == 0) + { + size_t deep_right = find_next_candidate(right_pos, limit); + if (deep_right == 0) // not found anything + right_check = -1; // pretend this element doesn't exist + else + { + right_check = 1; + right_pos = deep_right; + } + } + + // SINCE THIS LINE ON: + // Both left_check and right_check can be either 1 or -1. + + // But potentially can have only left == -1. + + if (left_check == -1) + { + if (right_check == -1) + return 0; + + // Otherwise we have left: -1 , right : 1 + return right_pos; + } + + // [[assert(left_check == 1)]] + // right_check can be 1 or -1 + + if (right_check == 1) // Meaning: "BOTH", select the best one. + { + // Return right only if it's better. + if (Access::order(keyat(left_pos), keyat(right_pos))) + return left_pos; + return right_pos; + } + + // Otherwise right_check is -1, so left is the only one. + // (this branch is execited if left_check == 1). + return left_pos; + } + NodeType pop_last() { NodeType out = m_HeapArray[m_HeapArray.size()-1]; From c7e85ad77dc8bc76dae75efb19702e321165a405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 14 Aug 2025 14:44:14 +0200 Subject: [PATCH 06/26] Some log fixes --- srtcore/core.cpp | 7 +++++-- srtcore/core.h | 4 +++- srtcore/schedule_snd.cpp | 4 +++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 3c1694e04..e770e9a82 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -10058,7 +10058,7 @@ bool CUDT::packData(const SchedPacket& spec, CPacket& w_packet, CNetworkInterfac time_point enter_time = steady_clock::now(); w_src_addr = m_SourceAddr; - IF_HEAVY_LOGGING(const char* reason); // The source of the data packet (normal/rexmit/filter) + IF_HEAVY_LOGGING(const char* reason = ""); // The source of the data packet (normal/rexmit/filter) if (spec.type() == sched::TP_REXMIT) { payload = packLostData((w_packet)); @@ -10080,6 +10080,7 @@ bool CUDT::packData(const SchedPacket& spec, CPacket& w_packet, CNetworkInterfac { LOGC(qslog.Error, log << CONID() << "filter: IPE: didn't provide control packet for %" << m_iSndCurrSeqNo << " that has been scheduled"); + return false; } } else // type() == TP_REGULAR @@ -10096,7 +10097,9 @@ bool CUDT::packData(const SchedPacket& spec, CPacket& w_packet, CNetworkInterfac w_packet.set_id(m_PeerID); // Set the destination SRT socket ID. - // XXX DELETE THIS. This should be done when scheduling the packet first time. + // XXX This should be done when scheduling the packet first time. + // The problem: m_PacketFilter has assigned affinity to the receiver worker thread, + // while scheduling a packet is happening in the application thread. if (new_packet_packed && m_PacketFilter) { HLOGC(qslog.Debug, log << CONID() << "filter: Feeding packet for source clip"); diff --git a/srtcore/core.h b/srtcore/core.h index 7ff16d8ae..9d40402e6 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -219,6 +219,8 @@ class CLastSched public: + static const int SCHEDULE_FORFEIT_LIMIT_MS = 500; + CLastSched() : m_iSchedSeqNo(SRT_SEQNO_NONE), m_iBufferedSeqNo(SRT_SEQNO_NONE), m_tdLastInterval(), m_bChain(false) {} sync::steady_clock::time_point lastTime() const { return m_tsTime.load(); } @@ -280,7 +282,7 @@ class CLastSched // The `interval` is the shortest possible time distance allowed // to be kept between sent packets. That's why we need to have // exactly 1s of positive distance so that the unused time forfeits. - if (forfeiture - m_tsTime.load() > sync::seconds_from(1)) + if (forfeiture - m_tsTime.load() > sync::milliseconds_from(SCHEDULE_FORFEIT_LIMIT_MS)) { // In case when the last send time recorded here is older by // more than 1 second from the current time carried back by 1 diff --git a/srtcore/schedule_snd.cpp b/srtcore/schedule_snd.cpp index b232e79cc..1ccb24ee9 100644 --- a/srtcore/schedule_snd.cpp +++ b/srtcore/schedule_snd.cpp @@ -97,7 +97,7 @@ SendTask::taskiter_t SendScheduler::enqueue_task(socket_t id, const SendTask& pr else { HLOGC(qslog.Debug, log << "Schedule: ENQ: new task at T=" << FormatTime(itask->m_tsSendTime) - << (was_first ? " (NEW TOP)" : "") << (!was_ready ? " (NOT READY YET)" : " (?)")); + << (was_first ? " (NEW TOP)" : "") << (!was_ready ? " (NOW READY)" : " (ONLY ADDED)")); } return itask; } @@ -155,6 +155,7 @@ bool SendScheduler::wait_extlock(UniqueLock& lk) m_TaskReadyCond.wait(lk); } + return true; } @@ -208,6 +209,7 @@ void SendScheduler::cancel(SendTask::taskiter_t itask) void SendScheduler::interrupt() { m_bBroken = true; + HLOGC(qslog.Debug, log << "Schedule: INTERRUPT: locking..."); sync::ScopedLock hold (m_Lock); HLOGC(qslog.Debug, log << "Schedule: INTERRUPT: notifying waiters"); From b4943f12b506f45456b2010847f24b87ff75cdae Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Thu, 6 Nov 2025 17:45:13 +0100 Subject: [PATCH 07/26] Cleared a compile warning --- srtcore/core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 763a950e5..f520dadbd 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -12244,7 +12244,7 @@ bool CUDT::defineSchedTimes(int32_t lo, int32_t hi, time_point& w_start, duratio { // XXX THIS IS NOT READY YET. KINDA STUB. - //int distance = CSeqNo::seqlen(lo, hi); + int distance SRT_ATR_UNUSED = CSeqNo::seqlen(lo, hi); time_point start = m_LastSend.time(); From 12c428573ed9b1adb7de2249539324f3eb68d235 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Thu, 6 Nov 2025 18:33:29 +0100 Subject: [PATCH 08/26] Removed another warning error --- srtcore/schedule_snd.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srtcore/schedule_snd.cpp b/srtcore/schedule_snd.cpp index 661c93165..7a2f5d7e3 100644 --- a/srtcore/schedule_snd.cpp +++ b/srtcore/schedule_snd.cpp @@ -123,7 +123,7 @@ bool SendScheduler::wait() bool SendScheduler::wait_extlock(UniqueLock& lk) { - typedef steady_clock::time_point ClockTime; + IF_HEAVY_LOGGING(typedef steady_clock::time_point ClockTime); for (;;) { if (m_bBroken) From 4f6016215989426b804112ae6c92f88cde589356 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Wed, 12 Nov 2025 14:58:00 +0100 Subject: [PATCH 09/26] Extracted CiBuffer from CRcvBuffer. Removed offset-dep from CSndBuffer. Put CSndLossList to CSndBuffer. Explicit save/restore for SndRateMeasurement --- docs/dev/containers.md | 322 +++++++++++++++++++++ srtcore/buffer_rcv.cpp | 17 +- srtcore/buffer_rcv.h | 605 ++++++++++++++++++--------------------- srtcore/buffer_snd.cpp | 211 ++++++++++---- srtcore/buffer_snd.h | 154 ++++++---- srtcore/buffer_tools.cpp | 19 +- srtcore/buffer_tools.h | 31 +- srtcore/common.h | 16 +- srtcore/core.cpp | 170 ++++------- srtcore/core.h | 18 +- srtcore/group.cpp | 4 +- srtcore/group_backup.cpp | 17 ++ srtcore/group_backup.h | 20 +- srtcore/utilities.h | 31 +- 14 files changed, 1017 insertions(+), 618 deletions(-) create mode 100644 docs/dev/containers.md diff --git a/docs/dev/containers.md b/docs/dev/containers.md new file mode 100644 index 000000000..7eb35b882 --- /dev/null +++ b/docs/dev/containers.md @@ -0,0 +1,322 @@ +Containers +========== + +Receiver buffer +--------------- + +The receiver buffer is based on the circular buffer with appropriate +enhancements. + +The circular buffer contains Entry internal type, which entails the status +and possibly the Unit containing the data. Units are supplied by a multiplexer +and placed by pointer here. + +Every time a packet comes in over the connection, which may be a control or +data packet, it's written into the buffer contained in a Unit. If such a unit +is filled with the control data, the control command is handled and the unit is +reused. If it contained the data, this is passed to the receiver buffer. The +position in the buffer is determined by the sequence number of the incoming +packet. The position is validated first, and if it turns out to be not filled +in before and within the expected range of arriving sequence numbers, the unit +filled during extraction of the data from the network is placed by pointer +into the appropriate position in the buffer. A unit that has been pinned into +the buffer is marked busy, and after extraction is marked free. + +On the other end packets are being extracted using various ways depending on +the mode: + +* live mode: always one packet at a time at position 0. If dropping is enabled, + the empty positions are just skipped until the readable packet is reached. + +* message mode: extraction is possible only if the whole message is + reassembled, although it may happen that there is available a complete + message, just not at the beginning of the buffer. If this message has + the `inorder` flag clear, it is allowed to be delivered even if it would + mean an out-of-order delivery. If this happens, all packets of this message + are marked as Read so that if the earlier message is finally completed, + after extracting this message the read messages directly following it + are also removed from the buffer + +* stream mode: extracted are as much packets from the ICR region as available + and fit in the given buffer. + +This is a rough schema of the receiver buffer: + +* ICR = Initial Contiguous Region: all cells here contain valid packets +* SCRAP REGION: Region with possibly filled or empty cells + - NOTE: in scrap region, the first cell is empty and the last one filled. +* SPARE REGION: Region without packets + +``` + + | BUSY REGION | + | | | | + | ICR | SCRAP REGION | SPARE REGION...-> + ......->| | | | + | /FIRST-GAP | | + |<------------------- hsize() ---------------------------->| + | |<------------ m_iMaxPosOff ----------->| | + | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ + | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] + +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ + | | | | + | | | \__last pkt received + |<------------->| m_iDropOff | + | | | + |<--------->| m_iEndOff | + | + \___ m_iStartPos: first packet position in the buffer + +``` + +Container value type is ReceiverBufferBase::Entry, which contains two +fields: + +* `pUnit`: points to the Unit, or NULL if empty +* `status`: the cell's status + +Status values used normally (live and stream mode): + +* `EntryState_Empty`: No packet here (pUnit == NULL). This is the default + state before getting a packet and the state of the units in the spare region + +* `EntryState_Avail`: The packet is ready for reading. It is set just after + the position has been confirmed and the unit pointer written into `pUnit` + +In message mode additionally two other states are possible: + +* `EntryState_Read`: The packet was prematurely extracted by out-of-order + reading. The space must be still occupied because it's in the scrap region + following some fragmented message, which still waits for reassembly + +* `EntryState_Drop`: The message was requested to be dropped. This usually + happens when the timeout for the message passed and the message is still + not reassembled, or when the message was revoked from the sender buffer + by the peer, so the peer has sent the `UMSG_DROPREQ`, making the packets + no longer recoverable. If this happens, all messages containing dropped + packets are marked Drop state and will no longer be delivered + +Position fields from the CiBuffer container: + +* `m_iStartPos`: physical container's index with the logical position 0 +* `m_iMaxPosOff`: past-the-end offset value (NOT position!) of the occupied region + +The container normally expects packets to be there in the order of their +sequence numbers. However, potentially packets can come out of order or +get lost in the UDP link; we can have then gaps in the form of unfilled +cells in the buffer. Hence we have 3 important regions: + +* ICR: This starts with the cell 0 and ends with the next empty cell. + ICR can be empty if the cell 0 is empty + +* FIRST GAP: Starts with the very first empty cell in the buffer + and follows until the first found filled cell. If the first empty + cell following the ICR is at `m_iMaxPosOff` potition, the first + gap is empty + +* DROP REGION: Starts with the first filled cell after a series of + empty cells of the FIRST GAP. Empty, if FIRST GAP is empty. + +These positions are marked with the following fields: + +* `m_iEndOff`: offset to the end of ICR. This points always to an empty cell. + If this value is 0, the buffer has an empty ICR. If this value is equal to + `m_iMaxPosOff`, the whole buffer is contiguous (no FIRST GAP or DROP REGION) + +* `m_iDropOff`: offset to a packet available for retrieval after a drop. If 0, + the DROP REGION is empty. DROP REGION is the fragment of the SCRAP REGION + that begins with at least one valid packet + +The DROP REGION is determined in order to quickly perform the dropping +operation. In case when the last packet has been extracted from the ICR, the +cell 0 is empty. When the decision for an extraction-over-consistency is +undertaken (live mode with too-late-packet-drop enabled, when the play time +comes for the first packet in the DROP REGION), the whole FIRST GAP is removed +from the buffer so the beginning of the buffer shifts to the DROP REGION, which +becomes ICR this way, and then the valid packet at cell 0 is extracted. + +Operational rules: + +Initially: + +``` + m_iStartPos = 0 + m_iEndOff = 0 + m_iDropOff = 0 +``` + +When a packet has arrived, then depending on where it landed: + +1. Position: next to the last read one and newest + +* `m_iStartPos` unchanged. +* `m_iEndOff` shifted by 1 +* `m_iDropOff` = 0 + +2. Position: after a loss, newest. + +* `m_iStartPos` unchanged. +* `m_iEndOff` unchanged. +* `m_iDropOff`: + - set to this packet, if `m_iDropOff` == 0 + - otherwise unchanged + +3. Position: after a loss, but belated (retransmitted) -- not equal to `m_iEndPos` + +* `m_iStartPos` unchanged. +* `m_iEndOff` unchanged. +* `m_iDropOff`: + - if `m_iDropOff` == 0, set to this + - if `m_iDropOff` is past this packet, set to this + - otherwise unchanged + +4. Position: after a loss, sealing -- seq equal to position of `m_iEndOff` + +* `m_iStartPos` unchanged. +* `m_iEndOff`: + - since this position, search the first empty cell + - stop at `m_iMaxPosOff` or first empty cell, whichever is found first +* `m_iDropOff`: + - if `m_iEndOff` == `m_iMaxPosOff`, set it to 0 + - otherwise search for a nonempty cell since `m_iEndOff` + - walk at maximum to `m_iMaxPosOff` + +NOTE: + +If DROP REGION is empty, then `m_iMaxPosOff` == `m_iEndOff`. If there is one +existing packet, then one loss, then two existing packets, the offsets are: + +* `m_iEndOff` = 1 +* `m_iDropOff` = 2 +* `m_iMaxPosOff` = 4 + +To wrap up: + +Let's say we have the following possibilities in a general scheme: + + +``` + [D] [C] [B] [A] (insertion cases) + | (start) --- (end) ===[gap]=== (after-loss) ... (max-pos) | + ICR FIRST GAP +``` + +See the CRcvBuffer::updatePosInfo method for detailed implementation. + +WHEN INSERTING A NEW PACKET: + +If the incoming sequence maps to newpktpos that is: + +* newpktpos <% (start) : discard the packet and exit +* newpktpos %> (size) : report discrepancy, discard and exit +* newpktpos %> (start) and: + * EXISTS: discard and exit (NOTE: could be also < (end)) + +``` +[A]* seq == `m_iMaxPosOff` + --> INC `m_iMaxPosOff` + * `m_iEndPos` == previous `m_iMaxPosOff` + * previous `m_iMaxPosOff` + 1 == `m_iMaxPosOff` + --> `m_iEndPos` = `m_iMaxPosOff` + --> `m_iDropPos` = `m_iEndPos` + * otherwise (means the new packet caused a gap) + --> `m_iEndPos` REMAINS UNCHANGED + --> `m_iDropPos` = POSITION(`m_iMaxPosOff`) +``` +COMMENT: + +If this above condition isn't satisfied, then there are gaps, first at +`m_iEndOff`, and `m_iDropOff` is at furthest equal to `m_iMaxPosOff`-1. The +inserted packet is outside both the contiguous region and the following +scratched region, so no updates on `m_iEndPos` and `m_iDropPos` are necessary. + +NOTE: + +SINCE THIS PLACE seq cannot be a sequence of an existing packet, +which means that earliest offset(newpktpos) == `m_iEndOff`, +up to == `m_iMaxPosOff` - 2. + +``` + * otherwise (newpktpos <% max-pos): + [D]* newpktpos == `m_iEndPos`: + --> (search FIRST GAP and FIRST AFTER-GAP) + --> `m_iEndPos`: increase until reaching `m_iMaxPosOff` + * `m_iEndPos` <% `m_iMaxPosOff`: + --> `m_iDropPos` = first VALID packet since `m_iEndPos` +% 1 + * otherwise: + --> `m_iDropPos` = `m_iEndPos` + [B]* newpktpos %> `m_iDropPos` + --> store, but do not update anything + [C]* otherwise (newpktpos %> `m_iEndPos` && newpktpos <% `m_iDropPos`) + --> store + --> set `m_iDropPos` = newpktpos +``` +COMMENT: + +It is guaratneed that between `m_iEndOff` and `m_iDropOff` there is only a gap +(series of empty cells). So wherever this packet lands, if it's next to +`m_iEndOff` and before `m_iDropOff` it will be the only packet that violates +the gap, hence this can be the only drop pos preceding the previous +`m_iDropOff`. + +Information returned to the caller should contain: + +1. Whether adding to the buffer was successful. + +2. Whether the "freshest" retrievable packet has been changed, that is: + * in live mode, a newly added packet has earlier delivery time than one before + * in stream mode, the newly added packet was at cell[0] + * in message mode, if the newly added packet has: + * completed the very first message + * completed any message further than first that has out-of-order flag + +The information about a changed packet is important for the caller in +live mode in order to notify the TSBPD thread. + + +WHEN CHECKING A PACKET + +1. Check the position at `m_iStartPos`. If there is a packet, return info at +its position. + +2. If position on `m_iStartPos` is empty, get the position of `m_iDropOff`. + +NOTE THAT: + + * if the buffer is empty, `m_iDropOff` and `m_iEndOff` are both 0 + + * if there is a packet in the buffer, but the first cell is empty, + then `m_iDropOff` points to this packet, while `m_iEndOff` == 0. + So after getting empty at cell 0 and `m_iDropOff` != 0, you can + read with dropping. + * If cell[0] is valid, there could be only at worst cell[1] empty + and cell[2] pointed by `m_iDropOff`. + +Note: `m_iDropOff` is updated every time a new packet arrives, even +if there are still not extracted packets in the ICR. + +3. In case of time-based checking for live mode, return empty packet info, +if this packet's play time is in the future. + +WHEN EXTRACTING A PACKET + +1. Extraction is only possible if there is a packet at cell[0]. +2. If there's no packet at cell[0], the application may request to + drop up to the given packet, or drop the whole message up to + the beginning of the next message. +3. In message mode, extraction can only extract a full message, so + if there's no full message ready, nothing is extracted. +4. When the extraction region is defined, the `m_iStartPos` is shifted + by the number of extracted packets. +5. After extracting packets, and therefore updated `m_iStartPos`, `m_iEndOff` + must be set again by searching for the first empty cell or reaching + `m_iMaxPosOff`. +6. If extraction involved dropping, `m_iDropOff` must be set again by + searching since `m_iEndOff`+1 to find the first valid packet. If + no such packet found before reaching `m_iMaxPosOff`, it's set to 0. +7. NOTE: all fields ending with `*Pos` are offsets, hence all of them must + be updated after `m_iStartPos` was changed. + + + diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 401b3f61e..e8ade2bef 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -99,15 +99,12 @@ namespace { */ CRcvBuffer::CRcvBuffer(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool bMessageAPI) - : m_entries(size) - , m_szSize(size) // TODO: maybe just use m_entries.size() + : BufferBase(size) , m_pUnitQueue(unitqueue) , m_iStartSeqNo(initSeqNo) // NOTE: SRT_SEQNO_NONE is allowed here. - , m_iStartPos(0) , m_iEndOff(0) , m_iDropOff(0) , m_iFirstNonreadPos(0) - , m_iMaxPosOff(0) , m_iNotch(0) , m_numNonOrderPackets(0) , m_iFirstNonOrderMsgPos(CPos_TRAP) @@ -175,7 +172,7 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) // TODO: Don't do assert here. Process this situation somehow. // If >= 2, then probably there is a long gap, and buffer needs to be reset. - SRT_ASSERT((m_iStartPos + offset) / m_szSize < 2); + SRT_ASSERT((m_iStartPos + offset) / hsize() < 2); const CPos newpktpos = incPos(m_iStartPos, offset); const COff prev_max_off = m_iMaxPosOff; @@ -191,7 +188,7 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) // possible even before checking that the packet // exists because existence of a packet beyond // the current max position is not possible). - SRT_ASSERT(newpktpos >= 0 && newpktpos < int(m_szSize)); + SRT_ASSERT(newpktpos >= 0 && newpktpos < int(hsize())); if (m_entries[newpktpos].status != EntryState_Empty) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -1"); @@ -548,7 +545,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro int iDropCnt = 0; const COff start_off = COff(max(0, offset_a)); const CPos start_pos = incPos(m_iStartPos, start_off); - const COff end_off = COff(min((int) m_szSize - 1, offset_b + 1)); + const COff end_off = COff(min((int) hsize() - 1, offset_b + 1)); const CPos end_pos = incPos(m_iStartPos, end_off); bool bDropByMsgNo = msgno > SRT_MSGNO_CONTROL; // Excluding both SRT_MSGNO_NONE (-1) and SRT_MSGNO_CONTROL (0). for (CPos i = start_pos; i != end_pos; i = incPos(i)) @@ -1497,7 +1494,7 @@ string CRcvBuffer::strFullnessState(int32_t iFirstUnackSeqNo, const time_point& ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo.val() << " m_iStartPos=" << m_iStartPos << " m_iMaxPosOff=" << m_iMaxPosOff << ". "; - ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << m_szSize << " pkts. "; + ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << hsize() << " pkts. "; if (m_tsbpd.isEnabled() && m_iMaxPosOff > 0) { @@ -1602,7 +1599,7 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) // find it earlier by checking packet presence. for (int off = offset; off < m_iMaxPosOff; ++off) { - CPos ipos ((m_iStartPos + off) % m_szSize); + CPos ipos ((m_iStartPos + off) % hsize()); if (m_entries[ipos].status == EntryState_Empty) { ret_seq = CSeqNo::incseq(m_iStartSeqNo.val(), off); @@ -1624,7 +1621,7 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) { for (int off = loss_off+1; off < m_iMaxPosOff; ++off) { - CPos ipos ((m_iStartPos + off) % m_szSize); + CPos ipos ((m_iStartPos + off) % hsize()); if (m_entries[ipos].status != EntryState_Empty) { *pw_end = CSeqNo::incseq(m_iStartSeqNo.val(), off); diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 75c98e647..9179095e3 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -197,211 +197,285 @@ typedef int COff; const int CPos_TRAP = -1; #endif +// Circular buffer base +template +struct CiBuffer +{ + typedef RandomAccessContainer entries_t; + typedef typename RandomAccessContainer::value_type value_type; + + entries_t m_entries; + CPos m_iStartPos; // the head position for I/O (inclusive) + + // ATOMIC: sometimes this value is checked for buffer emptiness + sync::atomic m_iMaxPosOff; // the furthest data position + + CiBuffer(size_t size): + m_entries(size), + m_iStartPos(0), + m_iMaxPosOff(0) + {} + + enum LoopStatus { BREAK, CONTINUE }; + + typedef LoopStatus entry_fn(value_type& e); + + // walkEntries: loop over the range of entries from startoff to endoff. + // @param startoff The first position to operate + // @param endoff The past-the-end of the last position + // @param fn Function to call matching the signature of @a entry_fn + // @retval CONTINUE all elements in the range have been passed + // @retval BREAK the loop was stopped in one of the iterations + // + // This function walks over the elements for the given offset range and + // executes the function. The function is free to modify the element and + // it should return CONTINUE, if after this iteration the loop should pass + // to the next element, or BREAK, if it should stop after this iteration. + // Elements are passed in the order of appearance, the implementation splits + // the range in two, if the end of container happens to be in the middle of + // the used range. + template + LoopStatus walkEntries(COff startoff, COff endoff, Callable fn) + { + SRT_ASSERT(startoff <= endoff && endoff <= hsize()); + + if (startoff == endoff) + return CONTINUE; + + // Use manual counting because endoff defines past-the-end, + // so the position shall be allowed to be equal to hsize(), + // which isn't possible with incPos(). + + CPos startpos, endpos; + + int start_avail = hsize() - m_iStartPos; + bool two_loops = false; + if (startoff > start_avail) // startoff is already off-range + { + int offshift = endoff - startoff; + startpos = m_iStartPos + startoff - hsize(); + // so end pos cannot be in the next section + endpos = startpos + offshift; + // Example: + // capacity=16 startpos=10 + // startoff=7 endoff=10 + // + // begin = 10+7 = 17 - 16 = 1; end = 20-16 = 4 + // One loop: [1] - [3] + } + else if (endoff < start_avail) // endoff fits, and so does startoff + { + startpos = m_iStartPos + startoff; + endpos = m_iStartPos + endoff; + // Example: + // capacity=16 startpos=10 + // startoff=0 endoff=5 + // + // begin = 10; end = 10+5 = 15 + // One loop: [10] - [14] + } + else + { + // We have a split-region. + startpos = m_iStartPos + startoff; + endpos = m_iStartPos + endoff - hsize(); + two_loops = true; + // Example: + // capacity=16 startpos=10 + // startoff=5 endoff=10 + // + // begin = 10+4 = 14; end = 10+10 = 20 - 16 = 4 + // First loop: [14] - [15] + // Second loop: [0] - [3] + } + + if (!two_loops) + { + for (CPos i = startpos; i < endpos; ++i) + { + value_type& e = m_entries[i]; + LoopStatus st = fn(e); + if (st == BREAK) + return BREAK; + } + return CONTINUE; + } + + for (CPos i = startpos; i < hsize(); ++i) + { + value_type& e = m_entries[i]; + LoopStatus st = fn(e); + if (st == BREAK) + return BREAK; + } + + for (CPos i = CPos(0); i < endpos; ++i) + { + value_type& e = m_entries[i]; + LoopStatus st = fn(e); + if (st == BREAK) + return BREAK; + } + + return CONTINUE; + } + + size_t hsize() const { return m_entries.size(); } + + //CPos incPos(CPos pos, COff inc = COff(1)) const { return CPos((pos + inc) % hsize()); } + + CPos incPos(CPos position, COff offset = COff(1)) const + { + CPos sum = position + offset; + CPos posmax = hsize(); + if (sum >= posmax) + return sum - posmax; + return sum; + } + CPos decPos(CPos pos) const + { + int diff = pos - 1; + if (diff >= 0) + { + return CPos(diff); + } + return CPos(hsize() - 1); + } + COff offPos(CPos pos1, CPos pos2) const + { + int diff = pos2 - pos1; + if (diff >= 0) + { + return COff(diff); + } + return COff(hsize() + diff); + } + + static COff decOff(COff val, int shift) + { + int ival = val - shift; + if (ival < 0) + return COff(0); + return COff(ival); + } + + bool empty() const + { + return (m_iMaxPosOff == COff(0)); + } + + /// Returns the currently used number of cells, including + /// gaps with empty cells, or in other words, the distance + /// between the initial position and the youngest received packet. + size_t size() const + { + return m_iMaxPosOff; + } + + // Returns true if the buffer is full. Requires locking. + bool full() const + { + return size() == capacity(); + } + + /// Return buffer capacity. + /// One slot had to be empty in order to tell the difference between "empty buffer" and "full buffer". + size_t capacity() const + { + return hsize() - 1; + } + + struct ClearGapEntries + { + LoopStatus operator()(value_type& v) const + { + v = value_type(); + return CONTINUE; + } + }; + + value_type* access(COff offset) + { + if (offset >= hsize()) + return NULL; + + if (offset >= m_iMaxPosOff) + { + walkEntries(m_iMaxPosOff, offset, ClearGapEntries()); + m_iMaxPosOff = offset + 1; + } + + return &m_entries[incPos(m_iStartPos, offset)]; + } + + void drop(COff offset) + { + if (offset >= m_iMaxPosOff) + { + walkEntries(0, m_iMaxPosOff, ClearGapEntries()); + + // Clear all + m_iStartPos = 0; + m_iMaxPosOff = 0; + return; + } + + walkEntries(0, offset, ClearGapEntries()); + m_iStartPos = incPos(m_iStartPos, offset); + m_iMaxPosOff = m_iMaxPosOff - offset; + } + +}; + +struct ReceiverBufferBase +{ + enum EntryStatus + { + EntryState_Empty, //< No CUnit record. + EntryState_Avail, //< Entry is available for reading. + EntryState_Read, //< Entry has already been read (out of order). + EntryState_Drop //< Entry has been dropped. + }; + + // TODO: Call makeUnitTaken upon assignment, and makeUnitFree upon clearing. + // TODO: CUnitPtr is not in use at the moment, but may be a smart pointer. + // class CUnitPtr + // { + // public: + // void operator=(CUnit* pUnit) + // { + // if (m_pUnit != NULL) + // { + // // m_pUnitQueue->makeUnitFree(m_entries[i].pUnit); + // } + // m_pUnit = pUnit; + // } + // private: + // CUnit* m_pUnit; + // }; + + struct Entry + { + Entry() + : pUnit(NULL) + , status(EntryState_Empty) + {} + + CUnit* pUnit; + EntryStatus status; + }; + + typedef FixedArray entries_t; +}; + + // // Circular receiver buffer. -// -// ICR = Initial Contiguous Region: all cells here contain valid packets -// SCRAP REGION: Region with possibly filled or empty cells -// NOTE: in scrap region, the first cell is empty and the last one filled. -// SPARE REGION: Region without packets -// -// | BUSY REGION | -// | | | | -// | ICR | SCRAP REGION | SPARE REGION...-> -// ......->| | | | -// | /FIRST-GAP | | -// |<------------------- m_szSize ---------------------------->| -// | |<------------ m_iMaxPosOff ----------->| | -// | | | | | | -// +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ -// | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] -// +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ -// | | | | -// | | | \__last pkt received -// |<------------->| m_iDropOff | -// | | | -// |<--------->| m_iEndOff | -// | -// \___ m_iStartPos: first packet position in the buffer -// -// m_pUnit[i]->status: -// EntryState_Empty: No packet was ever received here -// EntryState_Avail: The packet is ready for reading -// EntryState_Read: The packet is non-order-read -// EntryState_Drop: The packet was requested to drop -// -// thread safety: -// m_iStartPos: CUDT::m_RecvLock -// first_unack_pos_: CUDT::m_AckLock -// m_iMaxPosOff: none? (modified on add and ack -// m_iFirstNonreadPos: -// -// -// m_iStartPos: the first packet that should be read (might be empty) -// m_iEndOff: shift to the end of contiguous range. This points always to an empty cell. -// m_iDropPos: shift a packet available for retrieval after a drop. If 0, no such packet. -// -// Operational rules: -// -// Initially: -// m_iStartPos = 0 -// m_iEndOff = 0 -// m_iDropOff = 0 -// -// When a packet has arrived, then depending on where it landed: -// -// 1. Position: next to the last read one and newest -// -// m_iStartPos unchanged. -// m_iEndOff shifted by 1 -// m_iDropOff = 0 -// -// 2. Position: after a loss, newest. -// -// m_iStartPos unchanged. -// m_iEndOff unchanged. -// m_iDropOff: -// - set to this packet, if m_iDropOff == 0 or m_iDropOff is past this packet -// - otherwise unchanged -// -// 3. Position: after a loss, but belated (retransmitted) -- not equal to m_iEndPos -// -// m_iStartPos unchanged. -// m_iEndPos unchanged. -// m_iDropPos: -// - if m_iDropPos == m_iEndPos, set to this -// - if m_iDropPos %> this sequence, set to this -// - otherwise unchanged -// -// 4. Position: after a loss, sealing -- seq equal to position of m_iEndPos -// -// m_iStartPos unchanged. -// m_iEndPos: -// - since this position, search the first free cell -// - if reached the end of filled region (m_iMaxPosOff), stay there. -// m_iDropPos: -// - start from the value equal to m_iEndPos -// - walk at maximum to m_iMaxPosOff -// - find the first existing packet -// NOTE: -// If there are no "after gap" packets, then m_iMaxPosOff == m_iEndPos. -// If there is one existing packet, then one loss, then one packet, it -// should be that m_iEndPos = m_iStartPos %+ 1, m_iDropPos can reach -// to m_iStartPos %+ 2 position, and m_iMaxPosOff == m_iStartPos %+ 3. -// -// To wrap up: -// -// Let's say we have the following possibilities in a general scheme: -// -// -// [D] [C] [B] [A] (insertion cases) -// | (start) --- (end) ===[gap]=== (after-loss) ... (max-pos) | -// -// See the CRcvBuffer::updatePosInfo method for detailed implementation. -// -// WHEN INSERTING A NEW PACKET: -// -// If the incoming sequence maps to newpktpos that is: -// -// * newpktpos <% (start) : discard the packet and exit -// * newpktpos %> (size) : report discrepancy, discard and exit -// * newpktpos %> (start) and: -// * EXISTS: discard and exit (NOTE: could be also < (end)) -// [A]* seq == m_iMaxPosOff -// --> INC m_iMaxPosOff -// * m_iEndPos == previous m_iMaxPosOff -// * previous m_iMaxPosOff + 1 == m_iMaxPosOff -// --> m_iEndPos = m_iMaxPosOff -// --> m_iDropPos = m_iEndPos -// * otherwise (means the new packet caused a gap) -// --> m_iEndPos REMAINS UNCHANGED -// --> m_iDropPos = POSITION(m_iMaxPosOff) -// COMMENT: -// If this above condition isn't satisfied, then there are -// gaps, first at m_iEndPos, and m_iDropPos is at furthest -// equal to m_iMaxPosOff %- 1. The inserted packet is outside -// both the contiguous region and the following scratched region, -// so no updates on m_iEndPos and m_iDropPos are necessary. -// -// NOTE -// SINCE THIS PLACE seq cannot be a sequence of an existing packet, -// which means that earliest newpktpos == m_iEndPos, up to == m_iMaxPosOff -% 2. -// -// * otherwise (newpktpos <% max-pos): -// [D]* newpktpos == m_iEndPos: -// --> (search FIRST GAP and FIRST AFTER-GAP) -// --> m_iEndPos: increase until reaching m_iMaxPosOff -// * m_iEndPos <% m_iMaxPosOff: -// --> m_iDropPos = first VALID packet since m_iEndPos +% 1 -// * otherwise: -// --> m_iDropPos = m_iEndPos -// [B]* newpktpos %> m_iDropPos -// --> store, but do not update anything -// [C]* otherwise (newpktpos %> m_iEndPos && newpktpos <% m_iDropPos) -// --> store -// --> set m_iDropPos = newpktpos -// COMMENT: -// It is guaratneed that between m_iEndPos and m_iDropPos -// there is only a gap (series of empty cells). So wherever -// this packet lands, if it's next to m_iEndPos and before m_iDropPos -// it will be the only packet that violates the gap, hence this -// can be the only drop pos preceding the previous m_iDropPos. -// -// -- information returned to the caller should contain: -// 1. Whether adding to the buffer was successful. -// 2. Whether the "freshest" retrievable packet has been changed, that is: -// * in live mode, a newly added packet has earlier delivery time than one before -// * in stream mode, the newly added packet was at cell[0] -// * in message mode, if the newly added packet has: -// * completed the very first message -// * completed any message further than first that has out-of-order flag -// -// The information about a changed packet is important for the caller in -// live mode in order to notify the TSBPD thread. -// -// -// -// WHEN CHECKING A PACKET -// -// 1. Check the position at m_iStartPos. If there is a packet, -// return info at its position. -// -// 2. If position on m_iStartPos is empty, get the value of m_iDropPos. -// -// NOTE THAT: -// * if the buffer is empty, m_iDropPos == m_iStartPos and == m_iEndPos; -// note that m_iDropPos == m_iStartPos suffices to check that -// * if there is a packet in the buffer, but the first cell is empty, -// then m_iDropPos points to this packet, while m_iEndPos == m_iStartPos. -// Check then m_iStartPos == m_iEndPos to recognize it, and if then -// m_iDropPos isn't equal to them, you can read with dropping. -// * If cell[0] is valid, there could be only at worst cell[1] empty -// and cell[2] pointed by m_iDropPos. -// -// 3. In case of time-based checking for live mode, return empty packet info, -// if this packet's time is later than given time. -// -// WHEN EXTRACTING A PACKET -// -// 1. Extraction is only possible if there is a packet at cell[0]. -// 2. If there's no packet at cell[0], the application may request to -// drop up to the given packet, or drop the whole message up to -// the beginning of the next message. -// 3. In message mode, extraction can only extract a full message, so -// if there's no full message ready, nothing is extracted. -// 4. When the extraction region is defined, the m_iStartPos is shifted -// by the number of extracted packets. -// 5. If m_iEndPos <% m_iStartPos (after update), m_iEndPos should be -// set by searching from m_iStartPos up to m_iMaxPosOff for an empty cell. -// 6. m_iDropPos must be always updated. If m_iEndPos == m_iMaxPosOff, -// m_iDropPos is set to their value. Otherwise start from m_iEndPos -// and search a valid packet up to m_iMaxPosOff. -// 7. NOTE: m_iMaxPosOff is a delta, hence it must be set anew after update -// for m_iStartPos. -// +// The detailed description is provided in docs/dev/containers.md -class CRcvBuffer +class CRcvBuffer: public ReceiverBufferBase, private CiBuffer< FixedArray > { + typedef CiBuffer< FixedArray > BufferBase; typedef sync::steady_clock::time_point time_point; typedef sync::steady_clock::duration duration; @@ -410,6 +484,12 @@ class CRcvBuffer ~CRcvBuffer(); + // Publish some of the methods; everything else stays private. + using BufferBase::empty; + using BufferBase::full; + using BufferBase::size; + using BufferBase::capacity; + public: void debugShowState(const char* source); @@ -625,34 +705,6 @@ class CRcvBuffer int32_t getFirstLossSeq(int32_t fromseq, int32_t* opt_end = NULL); - bool empty() const - { - return (m_iMaxPosOff == COff(0)); - } - - /// Returns the currently used number of cells, including - /// gaps with empty cells, or in other words, the distance - /// between the initial position and the youngest received packet. - size_t size() const - { - return m_iMaxPosOff; - } - - // Returns true if the buffer is full. Requires locking. - bool full() const - { - return size() == capacity(); - } - - /// Return buffer capacity. - /// One slot had to be empty in order to tell the difference between "empty buffer" and "full buffer". - /// E.g. m_iFirstNonreadPos would again point to m_iStartPos if m_szSize entries are added continiously. - /// TODO: Old receiver buffer capacity returns the actual size. Check for conflicts. - size_t capacity() const - { - return m_szSize - 1; - } - int64_t getDrift() const { return m_tsbpd.drift(); } // TODO: make thread safe? @@ -680,58 +732,6 @@ class CRcvBuffer const CUnit* peek(int32_t seqno); private: - //* - CPos incPos(CPos pos, COff inc = COff(1)) const { return CPos((pos + inc) % m_szSize); } - CPos decPos(CPos pos) const { return (pos - 1) >= 0 ? CPos(pos - 1) : CPos(m_szSize - 1); } - COff offPos(CPos pos1, CPos pos2) const - { - int diff = pos2 - pos1; - if (diff >= 0) - { - return COff(diff); - } - return COff(m_szSize + diff); - } - - COff posToOff(CPos pos) const { return offPos(m_iStartPos, pos); } - - static COff decOff(COff val, int shift) - { - int ival = val - shift; - if (ival < 0) - return COff(0); - return COff(ival); - } - - /// @brief Compares the two positions in the receiver buffer relative to the starting position. - /// @param pos2 a position in the receiver buffer. - /// @param pos1 a position in the receiver buffer. - /// @return a positive value if pos2 is ahead of pos1; a negative value, if pos2 is behind pos1; otherwise returns 0. - inline COff cmpPos(CPos pos2, CPos pos1) const - { - // XXX maybe not the best implementation, but this keeps up to the rule. - // Maybe use m_iMaxPosOff to ensure a position is not behind the m_iStartPos. - - return posToOff(pos2) - posToOff(pos1); - } - // */ - - // Check if iFirstNonreadPos is in range [iStartPos, (iStartPos + iMaxPosOff) % iSize]. - // The right edge is included because we expect iFirstNonreadPos to be - // right after the last valid packet position if all packets are available. - static bool isInRange(CPos iStartPos, COff iMaxPosOff, size_t iSize, CPos iFirstNonreadPos) - { - if (iFirstNonreadPos == iStartPos) - return true; - - const CPos iLastPos = CPos((iStartPos + iMaxPosOff) % int(iSize)); - const bool isOverrun = iLastPos < iStartPos; - - if (isOverrun) - return iFirstNonreadPos > iStartPos || iFirstNonreadPos <= iLastPos; - - return iFirstNonreadPos > iStartPos && iFirstNonreadPos <= iLastPos; - } bool isInUsedRange(CPos iFirstNonreadPos) { @@ -741,7 +741,7 @@ class CRcvBuffer // DECODE the iFirstNonreadPos int diff = iFirstNonreadPos - m_iStartPos; if (diff < 0) - diff += m_szSize; + diff += hsize(); return diff <= m_iMaxPosOff; } @@ -792,57 +792,16 @@ class CRcvBuffer int getTimespan_ms() const; private: - // TODO: Call makeUnitTaken upon assignment, and makeUnitFree upon clearing. - // TODO: CUnitPtr is not in use at the moment, but may be a smart pointer. - // class CUnitPtr - // { - // public: - // void operator=(CUnit* pUnit) - // { - // if (m_pUnit != NULL) - // { - // // m_pUnitQueue->makeUnitFree(m_entries[i].pUnit); - // } - // m_pUnit = pUnit; - // } - // private: - // CUnit* m_pUnit; - // }; - - enum EntryStatus - { - EntryState_Empty, //< No CUnit record. - EntryState_Avail, //< Entry is available for reading. - EntryState_Read, //< Entry has already been read (out of order). - EntryState_Drop //< Entry has been dropped. - }; - struct Entry - { - Entry() - : pUnit(NULL) - , status(EntryState_Empty) - {} - CUnit* pUnit; - EntryStatus status; - }; - - typedef FixedArray entries_t; - entries_t m_entries; - - const size_t m_szSize; // size of the array of units (buffer) CUnitQueue* m_pUnitQueue; // the shared unit queue // ATOMIC because getStartSeqNo() may be called from other thread // than CUDT's receiver worker thread. Even if it's not a problem // if this value is a bit outdated, it must be read solid. SeqNoT< sync::atomic > m_iStartSeqNo; - CPos m_iStartPos; // the head position for I/O (inclusive) COff m_iEndOff; // past-the-end of the contiguous region since m_iStartOff COff m_iDropOff; // points past m_iEndOff to the first deliverable after a gap, or == m_iEndOff if no such packet CPos m_iFirstNonreadPos; // First position that can't be read (<= m_iLastAckPos) - // ATOMIC: sometimes this value is checked for buffer emptiness - sync::atomic m_iMaxPosOff; // the furthest data position int m_iNotch; // the starting read point of the first unit size_t m_numNonOrderPackets; // The number of stored packets with "inorder" flag set to false diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index b45120167..95645e696 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -65,57 +65,37 @@ using namespace std; using namespace srt::logging; using namespace sync; -CSndBuffer::CSndBuffer(int ip_family, int size, int maxpld, int authtag) +CSndBuffer::CSndBuffer(size_t bytesize, size_t slicesize, size_t mss, size_t headersize, size_t reservedsize, int flwsize) : m_BufLock() + , m_iSndLastDataAck(SRT_SEQNO_NONE) + , m_SndLossList(flwsize * 2) , m_pBlock(NULL) , m_pFirstBlock(NULL) , m_pCurrBlock(NULL) , m_pLastBlock(NULL) , m_pBuffer(NULL) , m_iNextMsgNo(1) - , m_iSize(size) - , m_iBlockLen(maxpld) - , m_iAuthTagSize(authtag) + , m_iBlockLen(mss - headersize) + , m_iAuthTagSize(reservedsize) , m_iCount(0) , m_iBytesCount(0) - , m_rateEstimator(ip_family) { - // initial physical buffer of "size" - m_pBuffer = new Buffer; - m_pBuffer->m_pcData = new char[m_iSize * m_iBlockLen]; - m_pBuffer->m_iSize = m_iSize; - m_pBuffer->m_pNext = NULL; - - // circular linked list for out bound packets - m_pBlock = new Block; - Block* pb = m_pBlock; - char* pc = m_pBuffer->m_pcData; - - for (int i = 0; i < m_iSize; ++i) - { - pb->m_iMsgNoBitset = 0; - pb->m_pcData = pc; - pc += m_iBlockLen; + // XXX decide what would be better for the implementation - allocate a single + // slice, allocate all the memory in slices, or just ignore slicesize. + m_iSize = std::min(int(slicesize), countNumPacketsRequired(bytesize, m_iBlockLen)); - if (i < m_iSize - 1) - { - pb->m_pNext = new Block; - pb = pb->m_pNext; - } - } - pb->m_pNext = m_pBlock; - - m_pFirstBlock = m_pCurrBlock = m_pLastBlock = m_pBlock; + m_rateEstimator.setHeaderSize(headersize); + initialize(); setupMutex(m_BufLock, "Buf"); } CSndBuffer::~CSndBuffer() { - Block* pb = m_pBlock->m_pNext; + CSndBlock* pb = m_pBlock->m_pNext; while (pb != m_pBlock) { - Block* temp = pb; + CSndBlock* temp = pb; pb = pb->m_pNext; delete temp; } @@ -141,6 +121,9 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) const int iPktLen = getMaxPacketLen(); const int iNumBlocks = countNumPacketsRequired(len, iPktLen); + if (m_iSndLastDataAck == SRT_SEQNO_NONE) + m_iSndLastDataAck = w_seqno; + HLOGC(bslog.Debug, log << "addBuffer: needs=" << iNumBlocks << " buffers for " << len << " bytes. Taken=" << m_iCount.load() << "/" << m_iSize); @@ -171,7 +154,7 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) // If there's more than one packet, this function must increase it by itself // and then return the accordingly modified sequence number in the reference. - Block* s = m_pLastBlock; + CSndBlock* s = m_pLastBlock; if (w_msgno == SRT_MSGNO_NONE) // DEFAULT-UNCHANGED msgno supplied { @@ -259,7 +242,7 @@ int CSndBuffer::addBufferFromFile(fstream& ifs, int len) log << CONID() << "addBufferFromFile: adding " << iPktLen << " packets (" << len << " bytes) to send, msgno=" << m_iNextMsgNo); - Block* s = m_pLastBlock; + CSndBlock* s = m_pLastBlock; int total = 0; for (int i = 0; i < iNumBlocks; ++i) { @@ -344,7 +327,7 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, m_pCurrBlock->m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(kflgs); } - Block* p = m_pCurrBlock; + CSndBlock* p = m_pCurrBlock; w_packet.set_msgflags(m_pCurrBlock->m_iMsgNoBitset); w_srctime = m_pCurrBlock->m_tsOriginTime; m_pCurrBlock = m_pCurrBlock->m_pNext; @@ -378,31 +361,32 @@ CSndBuffer::time_point CSndBuffer::peekNextOriginal() const return m_pCurrBlock->m_tsOriginTime; } -int32_t CSndBuffer::getMsgNoAt(const int offset) +int32_t CSndBuffer::getMsgNoAtSeq(const int32_t seqno) { ScopedLock bufferguard(m_BufLock); - Block* p = m_pFirstBlock; + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); - if (p) - { - HLOGC(bslog.Debug, - log << "CSndBuffer::getMsgNoAt: FIRST MSG: size=" << p->m_iLength << " %" << p->m_iSeqNo << " #" - << p->getMsgSeq() << " !" << BufferStamp(p->m_pcData, p->m_iLength)); - } - - if (offset >= m_iCount) + if (offset < 0 || offset >= m_iCount) { // Prevent accessing the last "marker" block LOGC(bslog.Error, - log << "CSndBuffer::getMsgNoAt: IPE: offset=" << offset << " not found, max offset=" << m_iCount.load()); + log << "CSndBuffer::getMsgNoAtSeq: IPE: for %" << seqno << " offset=" << offset << " outside container; max offset=" << m_iCount.load()); return SRT_MSGNO_CONTROL; } + CSndBlock* p = m_pFirstBlock; + if (p) + { + HLOGC(bslog.Debug, + log << "CSndBuffer::getMsgNoAtSeq: FIRST MSG: size=" << p->m_iLength << " %" << p->m_iSeqNo << " #" + << p->getMsgSeq() << " !" << BufferStamp(p->m_pcData, p->m_iLength)); + } + // XXX Suboptimal procedure to keep the blocks identifiable // by sequence number. Consider using some circular buffer. int i; - Block* ee SRT_ATR_UNUSED = 0; + CSndBlock* ee SRT_ATR_UNUSED = 0; for (i = 0; i < offset && p; ++i) { ee = p; @@ -412,26 +396,28 @@ int32_t CSndBuffer::getMsgNoAt(const int offset) if (!p) { LOGC(bslog.Error, - log << "CSndBuffer::getMsgNoAt: IPE: offset=" << offset << " not found, stopped at " << i << " with #" + log << "CSndBuffer::getMsgNoAt: IPE: for %" << seqno << "offset=" << offset << " not found, stopped at " << i << " with #" << (ee ? ee->getMsgSeq() : SRT_MSGNO_NONE)); return SRT_MSGNO_CONTROL; } HLOGC(bslog.Debug, - log << "CSndBuffer::getMsgNoAt: offset=" << offset << " found, size=" << p->m_iLength << " %" << p->m_iSeqNo + log << "CSndBuffer::getMsgNoAt: for %" << seqno << " offset=" << offset << " found, size=" << p->m_iLength << " %" << p->m_iSeqNo << " #" << p->getMsgSeq() << " !" << BufferStamp(p->m_pcData, p->m_iLength)); return p->getMsgSeq(); } -int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) +int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) { // NOTE: w_packet.m_iSeqNo is expected to be set to the value // of the sequence number with which this packet should be sent. ScopedLock bufferguard(m_BufLock); - Block* p = m_pFirstBlock; + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + + CSndBlock* p = m_pFirstBlock; // XXX Suboptimal procedure to keep the blocks identifiable // by sequence number. Consider using some circular buffer. @@ -449,6 +435,8 @@ int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time int32_t last_seq = p->m_iSeqNo; #endif + w_packet.set_seqno(seqno); + // This is rexmit request, so the packet should have the sequence number // already set when it was once sent uniquely. SRT_ASSERT(p->m_iSeqNo == w_packet.seqno()); @@ -498,6 +486,8 @@ int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time w_drop.seqno[DropRange::BEGIN] = w_packet.seqno(); w_drop.seqno[DropRange::END] = CSeqNo::incseq(w_packet.seqno(), msglen - 1); + m_SndLossList.removeUpTo(w_drop.seqno[DropRange::END]); + // Note the rules: here `p` is pointing to the first block AFTER the // message to be dropped, so the end sequence should be one behind // the one for p. Note that the loop rolls until hitting the first @@ -531,10 +521,14 @@ int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time return readlen; } -sync::steady_clock::time_point CSndBuffer::getPacketRexmitTime(const int offset) +sync::steady_clock::time_point CSndBuffer::getRexmitTime(const int32_t seqno) { ScopedLock bufferguard(m_BufLock); - const Block* p = m_pFirstBlock; + const CSndBlock* p = m_pFirstBlock; + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + if (offset < 0 || offset >= m_iCount) + return sync::steady_clock::time_point(); // XXX Suboptimal procedure to keep the blocks identifiable // by sequence number. Consider using some circular buffer. @@ -548,10 +542,23 @@ sync::steady_clock::time_point CSndBuffer::getPacketRexmitTime(const int offset) return p->m_tsRexmitTime; } -void CSndBuffer::ackData(int offset) +bool CSndBuffer::revoke(int32_t seqno) { ScopedLock bufferguard(m_BufLock); + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + + // IF distance between m_iSndLastDataAck and ack is nonempty... + if (offset <= 0) + return false; + + // remove any loss that predates 'ack' (not to be considered loss anymore) + m_SndLossList.removeUpTo(CSeqNo::decseq(seqno)); + + // We don't check if the sequence is past the last scheduled one; + // worst case scenario we'll just clear up the whole buffer. + m_iSndLastDataAck = seqno; + bool move = false; for (int i = 0; i < offset; ++i) { @@ -566,6 +573,40 @@ void CSndBuffer::ackData(int offset) m_iCount = m_iCount - offset; updAvgBufSize(steady_clock::now()); + return true; +} + +int32_t CSndBuffer::popLostSeq(DropRange& w_drop) +{ + static const DropRange nodrop = { {SRT_SEQNO_NONE, SRT_SEQNO_NONE}, SRT_MSGNO_CONTROL }; + w_drop = nodrop; + + // First attempt returned nothing, so return nothing and nodrop. + int32_t seq = m_SndLossList.popLostSeq(); + if (seq == SRT_SEQNO_NONE) + return seq; + + if (CSeqNo::seqoff(m_iSndLastDataAck, seq) < 0) + { + // Always request dropping up to the currently earliest remembered + // sequence number in the buffer. The only other thing needed to be + // cleaned up is to remove those outdated seqs from the loss list. + w_drop.seqno[DropRange::BEGIN] = seq; + w_drop.seqno[DropRange::END] = m_iSndLastDataAck; + + // In case when the loss record contains any sequences + // behind m_iSndLastDataAck, collect and drop them. + for (;;) + { + seq = m_SndLossList.popLostSeq(); + if (seq == SRT_SEQNO_NONE || CSeqNo::seqoff(m_iSndLastDataAck, seq) >= 0) + { + break; + } + } + } + + return seq; } int CSndBuffer::getCurrBufSize() const @@ -669,19 +710,32 @@ int CSndBuffer::dropLateData(int& w_bytes, int32_t& w_first_msgno, const steady_ { m_pCurrBlock = m_pFirstBlock; } - m_iCount = m_iCount - dpkts; - m_iBytesCount -= dbytes; - w_bytes = dbytes; + if (dpkts) + { + m_iCount = m_iCount - dpkts; + + const int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, dpkts); + m_iSndLastDataAck = fakeack; + + const int32_t minlastack = CSeqNo::decseq(m_iSndLastDataAck); + m_SndLossList.removeUpTo(minlastack); + + m_iBytesCount -= dbytes; + } + + w_bytes = dbytes; // even if 0 // We report the increased number towards the last ever seen // by the loop, as this last one is the last received. So remained // (even if "should remain") is the first after the last removed one. w_first_msgno = ++MsgNo(msgno); + // Note: this will be 1 if no packets were removed, but in this case + // dpkts == 0 and the referenced results will not be interpreted. updAvgBufSize(steady_clock::now()); - return (dpkts); + return dpkts; } int CSndBuffer::dropAll(int& w_bytes) @@ -696,6 +750,39 @@ int CSndBuffer::dropAll(int& w_bytes) return dpkts; } +// This does the same as increase(); try to find common parts or make +// these two common. +void CSndBuffer::initialize() +{ + // initial physical buffer of "size" + m_pBuffer = new Buffer; + m_pBuffer->m_pcData = new char[m_iSize * m_iBlockLen]; + m_pBuffer->m_iSize = m_iSize; + m_pBuffer->m_pNext = NULL; + + // circular linked list for out bound packets + m_pBlock = new CSndBlock; + CSndBlock* pb = m_pBlock; + char* pc = m_pBuffer->m_pcData; + + for (int i = 0; i < m_iSize; ++i) + { + pb->m_iMsgNoBitset = 0; + pb->m_pcData = pc; + pc += m_iBlockLen; + + if (i < m_iSize - 1) + { + pb->m_pNext = new CSndBlock; + pb = pb->m_pNext; + } + } + pb->m_pNext = m_pBlock; + + m_pFirstBlock = m_pCurrBlock = m_pLastBlock = m_pBlock; + +} + void CSndBuffer::increase() { int unitsize = m_pBuffer->m_iSize; @@ -722,20 +809,20 @@ void CSndBuffer::increase() p->m_pNext = nbuf; // new packet blocks - Block* nblk = NULL; + CSndBlock* nblk = NULL; try { - nblk = new Block; + nblk = new CSndBlock; } catch (...) { delete nblk; throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } - Block* pb = nblk; + CSndBlock* pb = nblk; for (int i = 1; i < unitsize; ++i) { - pb->m_pNext = new Block; + pb->m_pNext = new CSndBlock; pb = pb->m_pNext; } diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index 2c0964068..97b0abc1a 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -56,6 +56,7 @@ modified by #include "srt.h" #include "packet.h" #include "buffer_tools.h" +#include "list.h" // The notation used for "circular numbers" in comments: // The "cicrular numbers" are numbers that when increased up to the @@ -71,6 +72,30 @@ modified by namespace srt { +struct CSndBlock +{ + typedef sync::steady_clock::time_point time_point; + char* m_pcData; // pointer to the data block + int m_iLength; // payload length of the block (excluding auth tag). + + int32_t m_iMsgNoBitset; // message number and special bit flags + int32_t m_iSeqNo; // sequence number for scheduling + time_point m_tsOriginTime; // block origin time (either provided from above or equals the time a message was submitted for sending. + time_point m_tsRexmitTime; // packet retransmission time + int m_iTTL; // time to live (milliseconds) + + CSndBlock* m_pNext; // next block + + int32_t getMsgSeq() + { + // NOTE: this extracts message ID with regard to REXMIT flag. + // This is valid only for message ID that IS GENERATED in this instance, + // not provided by the peer. This can be otherwise sent to the peer - it doesn't matter + // for the peer that it uses LESS bits to represent the message. + return m_iMsgNoBitset & MSGNO_SEQ::mask; + } +}; + class CSndBuffer { typedef sync::steady_clock::time_point time_point; @@ -83,11 +108,18 @@ class CSndBuffer // Currently just "unimplemented". std::string CONID() const { return ""; } - /// @brief CSndBuffer constructor. - /// @param size initial number of blocks (each block to store one packet payload). - /// @param maxpld maximum packet payload (including auth tag). - /// @param authtag auth tag length in bytes (16 for GCM, 0 otherwise). - CSndBuffer(int ip_family, int size, int maxpld, int authtag); + // We have the following split for a single packet: + // + // [ ----------------------------- MSS ---------------------------------------------] + // [HEADER(IP-dependent)][ ................... PAYLOAD .................. ][auth tag] + + CSndBuffer(size_t bytesize, // size limit in bytes (will be split into packets) + size_t slicesize, // size of the single memory chunk for payload buffers + size_t mss, // value of the MSS (default: 1500, take from settings) + size_t headersize, // size of the packet header (IP version dependent) + size_t reservedsize, // Size reserved in the payload, but not for use for data. + int flow_window_size // required for loss list init + ); ~CSndBuffer(); public: @@ -155,21 +187,27 @@ class CSndBuffer /// @retval >0 Length of the data read. /// @retval READ_NONE No data available or @a offset points out of the buffer occupied space. /// @retval READ_DROP The call requested data drop due to TTL exceeded, to be handled first. + // SRT_TSA_NEEDS_NONLOCKED(m_BufLock) + // int readData(const int offset, CPacket& w_packet, time_point& w_origintime, DropRange& w_drop); + SRT_TSA_NEEDS_NONLOCKED(m_BufLock) - int readData(const int offset, CPacket& w_packet, time_point& w_origintime, DropRange& w_drop); + int readOldPacket(int32_t seqno, CPacket& w_packet, time_point& w_origintime, DropRange& w_drop); /// Get the time of the last retransmission (if any) of the DATA packet. /// @param [in] offset offset from the last ACK point (backward sequence number difference) /// /// @return Last time of the last retransmission event for the corresponding DATA packet. + // SRT_TSA_NEEDS_NONLOCKED(m_BufLock) + // time_point getPacketRexmitTime(const int offset); + SRT_TSA_NEEDS_NONLOCKED(m_BufLock) - time_point getPacketRexmitTime(const int offset); + time_point getRexmitTime(int32_t seqno); /// Update the ACK point and may release/unmap/return the user data according to the flag. /// @param [in] offset number of packets acknowledged. - int32_t getMsgNoAt(const int offset); + int32_t getMsgNoAtSeq(int32_t seqno); - void ackData(int offset); + bool revoke(int32_t upto_seqno); // upto_seqno = past-the-end! /// Read size of data still in the sending list. /// @return Current size of the data in the sending list. @@ -179,70 +217,86 @@ class CSndBuffer int dropLateData(int& bytes, int32_t& w_first_msgno, const time_point& too_late_time); int dropAll(int& bytes); - void updAvgBufSize(const time_point& time); int getAvgBufSize(int& bytes, int& timespan); int getCurrBufSize(int& bytes, int& timespan) const; + /// Retrieve input bitrate in bytes per second + int getInputRate() const { return m_rateEstimator.getInputRate(); } - /// Het maximum payload length per packet. - int getMaxPacketLen() const; + void enableRateEstimationIf(bool enable) { m_rateEstimator.resetInputRateSmpPeriod(!enable); } - /// @brief Count the number of required packets to store the payload (message). - /// @param iPldLen the length of the payload to check. - /// @return the number of required data packets. - int countNumPacketsRequired(int iPldLen) const; + void saveEstimation(CRateEstimator& w_est) + { + w_est.saveFrom(m_rateEstimator); + } - /// @brief Count the number of required packets to store the payload (message). - /// @param iPldLen the length of the payload to check. - /// @param iMaxPktLen the maximum payload length of the packet (the value returned from getMaxPacketLen()). - /// @return the number of required data packets. - int countNumPacketsRequired(int iPldLen, int iMaxPktLen) const; + void restoreEstimation(const CRateEstimator& r) + { + m_rateEstimator.restoreFrom(r); + } /// @brief Get the buffering delay of the oldest message in the buffer. /// @return the delay value. SRT_TSA_NEEDS_NONLOCKED(m_BufLock) duration getBufferingDelay(const time_point& tnow) const; - uint64_t getInRatePeriod() const { return m_rateEstimator.getInRatePeriod(); } + /// @brief Count the number of required packets to store the payload (message). + /// @param iPldLen the length of the payload to check. + /// @return the number of required data packets. + int countNumPacketsRequired(int iPldLen) const; - /// Retrieve input bitrate in bytes per second - int getInputRate() const { return m_rateEstimator.getInputRate(); } + /// Get maximum payload length per packet. + int getMaxPacketLen() const; - void resetInputRateSmpPeriod(bool disable = false) { m_rateEstimator.resetInputRateSmpPeriod(disable); } + int32_t firstSeqNo() const { return m_iSndLastDataAck; } - const CRateEstimator& getRateEstimator() const { return m_rateEstimator; } + // Required in group sequence override + void overrideFirstSeqNo(int32_t seq) { m_iSndLastDataAck = seq; } - void setRateEstimator(const CRateEstimator& other) { m_rateEstimator = other; } + // Sender loss list management methods + void removeLossUpTo(int32_t seqno) + { + m_SndLossList.removeUpTo(seqno); + } + + int insertLoss(int32_t lo, int32_t hi) + { + return m_SndLossList.insert(lo, hi); + } + + int32_t popLostSeq(DropRange&); + + int getLossLength() + { + return m_SndLossList.getLossLength(); + } private: + + void updAvgBufSize(const time_point& time); + + /// @brief Count the number of required packets to store the payload (message). + /// @param iPldLen the length of the payload to check. + /// @param iMaxPktLen the maximum payload length of the packet (the value returned from getMaxPacketLen()). + /// @return the number of required data packets. + int countNumPacketsRequired(int iPldLen, int iMaxPktLen) const; + + //uint64_t getInRatePeriod() const { return m_rateEstimator.getInRatePeriod(); } + +private: + void initialize(); void increase(); private: mutable sync::Mutex m_BufLock; // used to synchronize buffer operation - struct Block - { - char* m_pcData; // pointer to the data block - int m_iLength; // payload length of the block (excluding auth tag). - - int32_t m_iMsgNoBitset; // message number - int32_t m_iSeqNo; // sequence number for scheduling - time_point m_tsOriginTime; // block origin time (either provided from above or equals the time a message was submitted for sending. - time_point m_tsRexmitTime; // packet retransmission time - int m_iTTL; // time to live (milliseconds) - - Block* m_pNext; // next block - - int32_t getMsgSeq() - { - // NOTE: this extracts message ID with regard to REXMIT flag. - // This is valid only for message ID that IS GENERATED in this instance, - // not provided by the peer. This can be otherwise sent to the peer - it doesn't matter - // for the peer that it uses LESS bits to represent the message. - return m_iMsgNoBitset & MSGNO_SEQ::mask; - } - - } * m_pBlock, *m_pFirstBlock, *m_pCurrBlock, *m_pLastBlock; + sync::atomic m_iSndLastDataAck; // The real last ACK that updates the sender buffer and loss list + CSndLossList m_SndLossList; // Sender loss list + + CSndBlock* m_pBlock; + CSndBlock* m_pFirstBlock; + CSndBlock* m_pCurrBlock; + CSndBlock* m_pLastBlock; // m_pBlock: The head pointer // m_pFirstBlock: The first block diff --git a/srtcore/buffer_tools.cpp b/srtcore/buffer_tools.cpp index 002fc26a6..70e462472 100644 --- a/srtcore/buffer_tools.cpp +++ b/srtcore/buffer_tools.cpp @@ -102,23 +102,28 @@ void AvgBufSize::update(const steady_clock::time_point& now, int pkts, int bytes m_dTimespanMAvg = avg_iir_w<1000, double>(m_dTimespanMAvg, timespan_ms, elapsed_ms); } -CRateEstimator::CRateEstimator(int family) +CRateEstimator::CRateEstimator() : m_iInRatePktsCount(0) , m_iInRateBytesCount(0) - , m_InRatePeriod(INPUTRATE_FAST_START_US) // 0.5 sec (fast start) + , m_ullInRatePeriod_us(INPUTRATE_FAST_START_US) // 0.5 sec (fast start) , m_iInRateBps(INPUTRATE_INITIAL_BYTESPS) - , m_iFullHeaderSize(CPacket::udpHeaderSize(family) + CPacket::HDR_SIZE) + , m_iFullHeaderSize(CPacket::udpHeaderSize(AF_INET) + CPacket::HDR_SIZE) {} +void CRateEstimator::setHeaderSize(size_t size) +{ + m_iFullHeaderSize = size; +} + void CRateEstimator::setInputRateSmpPeriod(int period) { - m_InRatePeriod = (uint64_t)period; //(usec) 0=no input rate calculation + m_ullInRatePeriod_us = (uint64_t)period; //(usec) 0=no input rate calculation } void CRateEstimator::updateInputRate(const time_point& time, int pkts, int bytes) { // no input rate calculation - if (m_InRatePeriod == 0) + if (m_ullInRatePeriod_us == 0) return; if (is_zero(m_tsInRateStartTime)) @@ -136,10 +141,10 @@ void CRateEstimator::updateInputRate(const time_point& time, int pkts, int bytes m_iInRateBytesCount += bytes; // Trigger early update in fast start mode - const bool early_update = (m_InRatePeriod < INPUTRATE_RUNNING_US) && (m_iInRatePktsCount > INPUTRATE_MAX_PACKETS); + const bool early_update = (m_ullInRatePeriod_us < INPUTRATE_RUNNING_US) && (m_iInRatePktsCount > INPUTRATE_MAX_PACKETS); const uint64_t period_us = count_microseconds(time - m_tsInRateStartTime); - if (!early_update && period_us <= m_InRatePeriod) + if (!early_update && period_us <= m_ullInRatePeriod_us) return; // Required Byte/sec rate (payload + headers) diff --git a/srtcore/buffer_tools.h b/srtcore/buffer_tools.h index 1c90a56a1..876f1c678 100644 --- a/srtcore/buffer_tools.h +++ b/srtcore/buffer_tools.h @@ -94,10 +94,11 @@ class CRateEstimator typedef sync::steady_clock::time_point time_point; typedef sync::steady_clock::duration duration; public: - CRateEstimator(int family); + CRateEstimator(); + void setHeaderSize(size_t size); public: - uint64_t getInRatePeriod() const { return m_InRatePeriod; } + uint64_t getInRatePeriod() const { return m_ullInRatePeriod_us; } /// Retrieve input bitrate in bytes per second int getInputRate() const { return m_iInRateBps; } @@ -112,7 +113,27 @@ class CRateEstimator void resetInputRateSmpPeriod(bool disable = false) { setInputRateSmpPeriod(disable ? 0 : INPUTRATE_FAST_START_US); } -private: // Constants + void updateFrom(const CRateEstimator& other) + { +#define IMPORT(field) field = other.field + IMPORT(m_iInRatePktsCount); + IMPORT(m_iInRateBytesCount); + IMPORT(m_tsInRateStartTime); + IMPORT(m_ullInRatePeriod_us); + IMPORT(m_iInRateBps); +#undef IMPORT + } + + template + void saveFrom(const Saver& o) { updateFrom(o); } + + template + void restoreFrom(const Saver& o) { updateFrom(o); } + +private: + CRateEstimator& operator=(const CRateEstimator&); // = delete; + + // Constants static const uint64_t INPUTRATE_FAST_START_US = 500000; // 500 ms static const uint64_t INPUTRATE_RUNNING_US = 1000000; // 1000 ms static const int64_t INPUTRATE_MAX_PACKETS = 2000; // ~ 21 Mbps of 1316 bytes payload @@ -122,12 +143,12 @@ class CRateEstimator int m_iInRatePktsCount; // number of payload packets added since InRateStartTime. int m_iInRateBytesCount; // number of payload bytes added since InRateStartTime. time_point m_tsInRateStartTime; - uint64_t m_InRatePeriod; // usec + uint64_t m_ullInRatePeriod_us; // usec int m_iInRateBps; // Input Rate in Bytes/sec int m_iFullHeaderSize; }; - +// XXX UNUSED class CSndRateEstimator { typedef sync::steady_clock::time_point time_point; diff --git a/srtcore/common.h b/srtcore/common.h index fcde8be06..905537673 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -78,21 +78,6 @@ modified by #define NET_ERROR WSAGetLastError() #endif -#ifdef _DEBUG -#if defined(SRT_ENABLE_THREADCHECK) -#include "threadcheck.h" -#define SRT_ASSERT(cond) ASSERT(cond) -#else -#include -#define SRT_ASSERT(cond) assert(cond) -#endif -#else -#define SRT_ASSERT(cond) -#endif - -/* -* SRT_ENABLE_THREADCHECK IS SET IN MAKEFILE, NOT HERE -*/ #if defined(SRT_ENABLE_THREADCHECK) #include "threadcheck.h" #else @@ -103,6 +88,7 @@ modified by #define INCREMENT_THREAD_ITERATIONS() #endif +// Defined here because it relies on SRT_ASSERT macro provided in utilities.h #define SRT_ASSERT_AFFINITY(id) SRT_ASSERT(::srt::sync::CheckAffinity(id)) // This is a log configuration used inside SRT. diff --git a/srtcore/core.cpp b/srtcore/core.cpp index a7533dc76..6221a99de 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -376,7 +376,6 @@ void CUDT::construct() { m_pSndBuffer = NULL; m_pRcvBuffer = NULL; - m_pSndLossList = NULL; m_pRcvLossList = NULL; m_iReorderTolerance = 0; // How many times so far the packet considered lost has been received @@ -534,7 +533,6 @@ CUDT::~CUDT() // destroy the data structures delete m_pSndBuffer; delete m_pRcvBuffer; - delete m_pSndLossList; delete m_pRcvLossList; } @@ -5929,7 +5927,8 @@ bool CUDT::prepareBuffers(CUDTException* eout) // The family as the first argument is something different - it's for the header size in order // to calculate rate and statistics. - int snd_payload_size = m_config.iMSS - CPacket::HDR_SIZE - CPacket::udpHeaderSize(m_TransferIPVersion); + int snd_header_size = CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion); + int snd_payload_size SRT_ATR_UNUSED = m_config.iMSS - snd_header_size; SRT_ASSERT(m_iMaxSRTPayloadSize <= snd_payload_size); HLOGC(rslog.Debug, log << CONID() << "Creating buffers: snd-plsize=" << snd_payload_size @@ -5937,11 +5936,10 @@ bool CUDT::prepareBuffers(CUDTException* eout) << (m_TransferIPVersion == AF_INET6 ? "6" : m_TransferIPVersion == AF_INET ? "4" : "???") << " authtag=" << authtag); - m_pSndBuffer = new CSndBuffer(m_TransferIPVersion, 32, snd_payload_size, authtag); + m_pSndBuffer = new CSndBuffer (m_config.iSndBufSize, 32, m_config.iMSS, snd_header_size, authtag, m_iFlowWindowSize); SRT_ASSERT(m_iPeerISN != -1); m_pRcvBuffer = new CRcvBuffer(m_iPeerISN, m_config.iRcvBufSize, m_pMuxer->getBufferQueue(), m_config.bMessageAPI); // After introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice a space. - m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2); m_pRcvLossList = new CRcvLossList(m_config.iFlightFlagSize); } catch (...) @@ -6801,6 +6799,9 @@ int CUDT::sndDropTooLate() // protect packet retransmission ScopedLock rcvlck(m_RecvAckLock); + + IF_HEAVY_LOGGING(const int32_t realack = m_pSndBuffer->firstSeqNo()); + int dbytes; int32_t first_msgno; const int dpkts = m_pSndBuffer->dropLateData((dbytes), (first_msgno), tnow - milliseconds_from(threshold_ms)); @@ -6814,20 +6815,11 @@ int CUDT::sndDropTooLate() m_stats.sndr.dropped.count(stats::BytesPackets((uint64_t) dbytes, (uint32_t) dpkts)); leaveCS(m_StatsLock); - IF_HEAVY_LOGGING(const int32_t realack = m_iSndLastDataAck); - const int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, dpkts); + m_iSndLastAck = m_pSndBuffer->firstSeqNo(); - m_iSndLastAck = fakeack; - m_iSndLastDataAck = fakeack; - - const int32_t minlastack = CSeqNo::decseq(m_iSndLastDataAck); - m_pSndLossList->removeUpTo(minlastack); /* If we dropped packets not yet sent, advance current position */ // THIS MEANS: m_iSndCurrSeqNo = MAX(m_iSndCurrSeqNo, m_iSndLastDataAck-1) - if (CSeqNo::seqcmp(m_iSndCurrSeqNo, minlastack) < 0) - { - m_iSndCurrSeqNo = minlastack; - } + m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, CSeqNo::decseq(m_iSndLastAck)); HLOGC(qslog.Debug, log << CONID() << "SND-DROP: %(" << realack << "-" << m_iSndCurrSeqNo.load() << ") n=" << dpkts << "pkt " << dbytes @@ -6962,7 +6954,7 @@ int CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) } // sndDropTooLate(...) may lock m_RecvAckLock - // to modify m_pSndBuffer and m_pSndLossList + // to modify m_pSndBuffer const int iPktsTLDropped SRT_ATR_UNUSED = sndDropTooLate(); // For MESSAGE API the minimum outgoing buffer space required is @@ -7974,8 +7966,7 @@ bool CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) else { // No need to calculate input rate if the bandwidth is set - const bool disable_in_rate_calc = (bw != 0); - m_pSndBuffer->resetInputRateSmpPeriod(disable_in_rate_calc); + m_pSndBuffer->enableRateEstimationIf(bw == 0); } HLOGC(rslog.Debug, @@ -8606,7 +8597,7 @@ int CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) return nbsent; } -void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) +void CUDT::revokeACKedSequences(int32_t ackdata_seqno) { #if SRT_ENABLE_BONDING // This is for the call of CSndBuffer::getMsgNoAt that returns @@ -8620,33 +8611,24 @@ void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) // m_RecvAckLock protects sender's loss list and epoll ScopedLock ack_lock(m_RecvAckLock); - const int offset = CSeqNo::seqoff(m_iSndLastDataAck, ackdata_seqno); - // IF distance between m_iSndLastDataAck and ack is nonempty... - if (offset <= 0) + // acknowledge the sending buffer (remove data that predate 'ack') + // False is returned if this seqno is already revoked. + if (!m_pSndBuffer->revoke(ackdata_seqno)) return; - // update sending variables - m_iSndLastDataAck = ackdata_seqno; - #if SRT_ENABLE_BONDING if (is_group) { - // Get offset-1 because 'offset' points actually to past-the-end - // of the sender buffer. We have already checked that offset is - // at least 1. - msgno_at_last_acked_seq = m_pSndBuffer->getMsgNoAt(offset-1); + // Get ackdata_seqno %- 1 because ackdata_seqno points actually to + // past-the-end of the sender buffer. We have already checked that + // offset is at least 1. + msgno_at_last_acked_seq = m_pSndBuffer->getMsgNoAtSeq(CSeqNo::decseq(ackdata_seqno)); // Just keep this value prepared; it can't be updated exactly right // now because accessing the group needs some locks to be applied // with preserved the right locking order. } #endif - // remove any loss that predates 'ack' (not to be considered loss anymore) - m_pSndLossList->removeUpTo(CSeqNo::decseq(m_iSndLastDataAck)); - - // acknowledge the sending buffer (remove data that predate 'ack') - m_pSndBuffer->ackData(offset); - // acknowledde any waiting epolls to write uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); CGlobEvent::triggerEvent(); @@ -8711,10 +8693,10 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point const bool isLiteAck = ctrlpkt.getLength() == (size_t)SEND_LITE_ACK; HLOGC(inlog.Debug, - log << CONID() << "ACK covers: " << m_iSndLastDataAck.load() << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck + log << CONID() << "ACK covers: " << m_pSndBuffer->firstSeqNo() << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck << "]" << (isLiteAck ? "[LITE]" : "[FULL]")); - updateSndLossListOnACK(ackdata_seqno); + revokeACKedSequences(ackdata_seqno); // Process a lite ACK if (isLiteAck) @@ -9103,7 +9085,7 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << losslist_lo << " - " << losslist_hi << " to loss list"); - num = m_pSndLossList->insert(losslist_lo, losslist_hi); + num = m_pSndBuffer->insertLoss(losslist_lo, losslist_hi); } // ELSE losslist_lo %< m_iSndLastAck else @@ -9127,7 +9109,7 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << m_iSndLastAck << "[ACK] - " << losslist_hi << " to loss list"); - num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi); + num = m_pSndBuffer->insertLoss(m_iSndLastAck, losslist_hi); dropreq_hi = CSeqNo::decseq(m_iSndLastAck); IF_HEAVY_LOGGING(drop_type = "partially"); } @@ -9169,7 +9151,7 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding %" << losslist[i] << " (1 packet) to loss list"); - const int num = m_pSndLossList->insert(losslist[i], losslist[i]); + const int num = m_pSndBuffer->insertLoss(losslist[i], losslist[i]); enterCS(m_StatsLock); m_stats.sndr.lost.count(num); @@ -9684,7 +9666,7 @@ void CUDT::updateAfterSrtHandshake(int hsv) // XXX During sender buffer refax turn this into either returning // a sequence number or move it to the sender buffer facility. // [[using locked (m_RecvAckLock)]] -pair CUDT::getCleanRexmitOffset() +int32_t CUDT::getCleanRexmitOffset() { // This function is required to look into the loss list and // drop all sequences that are alrady revoked from the sender @@ -9695,62 +9677,23 @@ pair CUDT::getCleanRexmitOffset() for (;;) { // REPEATABLE because we might need to drop this scheduled seq. + typedef CSndBuffer::DropRange DropRange; + DropRange buffer_drop; - // Pick up the first sequence. - int32_t seq = m_pSndLossList->popLostSeq(); - if (seq == SRT_SEQNO_NONE) - { - // No loss reports, we are clear here. - return std::make_pair(SRT_SEQNO_NONE, -1); - } + int32_t seq = m_pSndBuffer->popLostSeq((buffer_drop)); - int offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); - if (offset < 0) + // The droppable information MAY be reported regardless if any + // lost sequence was found. + if (buffer_drop.seqno[DropRange::BEGIN] != SRT_SEQNO_NONE) { - // XXX Likely that this will never be executed because if the upper - // sequence is not in the sender buffer, then most likely the loss - // was completely ignored. - LOGC(qslog.Error, log << CONID() - << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(seqno() " - << seq << ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset - << ". Continue, request DROP"); - - int32_t oldest_outdated_seq = seq; - // We have received the first outdated sequence. - // Loop on to find out more. - // Interrupt on the first non-outdated sequence - // or when the loss list gets empty. - - for (;;) - { - seq = m_pSndLossList->popLostSeq(); - if (seq == SRT_SEQNO_NONE) - { - offset = -1; // set it because this will be returned. - break; - } - - offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); - if (offset >= 0) - break; - } - - // Now we have 'seq' that is either non-sequence - // or a valid sequence. We can safely exit with this - // value from this branch. We just need to manage - // the drop report. - - // No matter whether this is right or not (maybe the attack case should be - // considered, and some LOSSREPORT flood prevention), send the drop request - // to the peer. int32_t seqpair[2] = { - oldest_outdated_seq, - CSeqNo::decseq(m_iSndLastDataAck) + buffer_drop.seqno[DropRange::BEGIN], + buffer_drop.seqno[DropRange::END] }; HLOGC(qslog.Debug, log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: %(" - << seqpair[0] << " - " << seqpair[1] << ") (" << (-offset) << " packets)"); + << seqpair[0] << " - " << seqpair[1] << ")"); // See interpretation in processCtrlDropReq(). We don't know the message number, // so we request that the drop be exclusively sequence number based. @@ -9758,13 +9701,16 @@ pair CUDT::getCleanRexmitOffset() sendCtrl(UMSG_DROPREQ, &msgno, seqpair, sizeof(seqpair)); } - // We have exit anyway with the right sequence or no sequence. + // Pick up the first sequence. if (seq == SRT_SEQNO_NONE) - return std::make_pair(SRT_SEQNO_NONE, -1); + { + // No loss reports, we are clear here. + return seq; + } // For the right sequence though check also the rigt time. const steady_clock::time_point time_now = steady_clock::now(); - if (!checkRexmitRightTime(offset, time_now)) + if (!checkRexmitRightTime(seq, time_now)) { // Ignore this, even though valid, sequence, and pick up // the next from the list. @@ -9772,19 +9718,19 @@ pair CUDT::getCleanRexmitOffset() } // Ok, we have what we needed one way or another. - return std::make_pair(seq, offset); + return seq; } } // [[using locked (m_RecvAckLock)]] -bool srt::CUDT::checkRexmitRightTime(int offset, const srt::sync::steady_clock::time_point& current_time) +bool srt::CUDT::checkRexmitRightTime(int32_t seqno, const srt::sync::steady_clock::time_point& current_time) { // If not configured, the time is always right if (!m_bPeerNakReport || m_config.iRetransmitAlgo == 0) return true; const steady_clock::time_point time_nak = current_time - microseconds_from(m_iSRTT - 4 * m_iRTTVar); - const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getPacketRexmitTime(offset); + const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getRexmitTime(seqno); if (tsLastRexmit >= time_nak) { @@ -9799,7 +9745,7 @@ bool srt::CUDT::checkRexmitRightTime(int offset, const srt::sync::steady_clock:: last_rextime << "REXMITTED " << FormatDuration(current_time - tsLastRexmit) << " ago at " << FormatTime(tsLastRexmit); } - HLOGC(qslog.Debug, log << CONID() << "REXMIT: ignoring %" << CSeqNo::incseq(m_iSndLastDataAck, offset) + HLOGC(qslog.Debug, log << CONID() << "REXMIT: ignoring %" << seqno << " " << last_rextime.str() << ", RTT: ~" << FormatValue(m_iSRTT, 1000, "ms") << " <>" << FormatValue(m_iRTTVar, 1000, "") @@ -9813,17 +9759,18 @@ bool srt::CUDT::checkRexmitRightTime(int offset, const srt::sync::steady_clock:: // [[using locked (m_RecvAckLock)]] -int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, int offset, CPacket& w_packet, - srt::sync::steady_clock::time_point& w_tsOrigin) +int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, CPacket& w_packet, srt::sync::steady_clock::time_point& w_tsOrigin) { // REPEATABLE BLOCK (not a real loop) + // The call to readOldPacket may result in a drop request, which must be + // handled and then the call repeated, until it returns a valid packet + // or no packet to retransmit. for (;;) { typedef CSndBuffer::DropRange DropRange; DropRange buffer_drop; - w_packet.set_seqno(seqno); - const int payload = m_pSndBuffer->readData(offset, (w_packet), (w_tsOrigin), (buffer_drop)); + const int payload = m_pSndBuffer->readOldPacket(seqno, (w_packet), (w_tsOrigin), (buffer_drop)); if (payload == CSndBuffer::READ_DROP) { SRT_ASSERT(CSeqNo::seqoff(buffer_drop.seqno[DropRange::BEGIN], buffer_drop.seqno[DropRange::END]) >= 0); @@ -9835,7 +9782,6 @@ int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, int offset, CPacket& w_pa sendCtrl(UMSG_DROPREQ, &buffer_drop.msgno, buffer_drop.seqno, sizeof(buffer_drop.seqno)); // skip all dropped packets - m_pSndLossList->removeUpTo(buffer_drop.seqno[DropRange::END]); m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, buffer_drop.seqno[DropRange::END]); continue; } @@ -9896,25 +9842,23 @@ int CUDT::packLostData(CPacket& w_packet) { // protect m_iSndLastDataAck from updating by ACK processing ScopedLock ackguard(m_RecvAckLock); - int32_t seqno; - int offset; // Get the first sequence for retransmission, bypassing and taking care of // those that are in the forgotten region, as well as required to be rejected. - Tie(seqno, offset) = getCleanRexmitOffset(); + int32_t seqno = getCleanRexmitOffset(); if (seqno == SRT_SEQNO_NONE) return 0; // Extract the packet from the sender buffer that is mapped to the expected sequence // number, bypassing and taking care of those that are decided to be dropped. - const int payload = extractCleanRexmitPacket(seqno, offset, (w_packet), (tsOrigin)); + const int payload = extractCleanRexmitPacket(seqno, (w_packet), (tsOrigin)); if (payload <= 0) return 0; } HLOGC(qslog.Debug, log << CONID() << "packed REXMIT packet %" << w_packet.seqno() << " size=" << w_packet.getLength() - << " - still " << m_pSndLossList->getLossLength() << " LOSS ENTRIES left"); + << " - still " << m_pSndBuffer->getLossLength() << " LOSS ENTRIES left"); // POST-EXTRACTION length fixes. @@ -10303,8 +10247,8 @@ bool CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, CNe #if HVU_ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr() HLOGC(qslog.Debug, - log << CONID() << "packData: " << reason << " packet seq=" << w_packet.seqno() << " (ACK=" << m_iSndLastAck - << " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << w_packet.MessageFlagStr() << ")"); + log << CONID() << "packData: " << reason << " packet %" << w_packet.seqno() << " (ACK=%" << m_iSndLastAck + << " ACKDATA=%" << m_pSndBuffer->firstSeqNo() << " MSG/FLAGS: " << w_packet.MessageFlagStr() << ")"); #endif // Fix keepalive @@ -10474,7 +10418,7 @@ bool CUDT::packUniqueData(CPacket& w_packet) ScopedLock ackguard(m_RecvAckLock); m_iSndCurrSeqNo = w_packet.seqno(); m_iSndLastAck = w_packet.seqno(); - m_iSndLastDataAck = w_packet.seqno(); + m_pSndBuffer->overrideFirstSeqNo(w_packet.seqno()); m_iSndLastFullAck = w_packet.seqno(); m_iSndLastAck2 = w_packet.seqno(); } @@ -10630,7 +10574,7 @@ bool CUDT::overrideSndSeqNo(int32_t seq) m_stats.sndr.dropped.count(dbytes);; leaveCS(m_StatsLock); - m_pSndLossList->removeUpTo(CSeqNo::decseq(seq)); + m_pSndBuffer->removeLossUpTo(CSeqNo::decseq(seq)); // The peer will have to do the same, as a reaction on perceived // packet loss. When it recognizes that this initial screwing up @@ -12264,7 +12208,7 @@ void CUDT::checkRexmitTimer(const steady_clock::time_point& currtime) // - in case of FASTREXMIT (Live Mode): the RTO (rtt_syn) was triggered, therefore // schedule unacknowledged packets for retransmission regardless of the loss list emptiness. - if ((!is_laterexmit || m_pSndLossList->getLossLength() == 0)) + if ((!is_laterexmit || m_pSndBuffer->getLossLength() == 0)) { ScopedLock acklock(m_RecvAckLock); // Protect packet retransmission if (getFlightSpan() > 0) @@ -12272,7 +12216,7 @@ void CUDT::checkRexmitTimer(const steady_clock::time_point& currtime) // Sender: Insert all the packets sent after last received acknowledgement into the sender loss list. // Resend all unacknowledged packets on timeout, but only if there is no packet in the loss list const int32_t csn = m_iSndCurrSeqNo; - const int num = m_pSndLossList->insert(m_iSndLastAck, csn); + const int num = m_pSndBuffer->insertLoss(m_iSndLastAck, csn); if (num > 0) { enterCS(m_StatsLock); diff --git a/srtcore/core.h b/srtcore/core.h index def0a90dc..bb927ec3e 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -475,7 +475,7 @@ class CUDT size_t OPT_PayloadSize() const { return m_config.zExpPayloadSize; } size_t payloadSize() const; - int sndLossLength() { return m_pSndLossList->getLossLength(); } + int sndLossLength() { return m_pSndBuffer->getLossLength(); } int32_t ISN() const { return m_iISN; } int32_t peerISN() const { return m_iPeerISN; } duration minNAKInterval() const { return m_tdMinNakInterval; } @@ -954,12 +954,12 @@ class CUDT static loss_seqs_t defaultPacketArrival(void* vself, CPacket& pkt); static loss_seqs_t groupPacketArrival(void* vself, CPacket& pkt); - void setRateEstimator(const CRateEstimator& rate) + void restoreRateEstimator(const CRateEstimator& rate) { if (!m_pSndBuffer) return; - m_pSndBuffer->setRateEstimator(rate); + m_pSndBuffer->restoreEstimation(rate); updateCC(TEV_SYNC, EventVariant(0)); } @@ -1049,7 +1049,6 @@ class CUDT private: // Sending related data CSndBuffer* m_pSndBuffer; // Sender buffer - CSndLossList* m_pSndLossList; // Sender loss list CPktTimeWindow<16, 16> m_SndTimeWindow; // Packet sending time window #ifdef SRT_ENABLE_MAXREXMITBW size_t m_zSndAveragePacketSize; @@ -1110,7 +1109,6 @@ class CUDT // to the sending buffer. This way, extraction of an old packet for retransmission should // require only the lost sequence number, and how to find the packet with this sequence // will be up to the sending buffer. - sync::atomic m_iSndLastDataAck; // The real last ACK that updates the sender buffer and loss list SRT_TSA_GUARDED_BY(m_RecvAckLock) sync::atomic m_iSndCurrSeqNo; // The largest sequence number that HAS BEEN SENT sync::atomic m_iSndNextSeqNo; // The sequence number predicted to be placed at the currently scheduled packet @@ -1134,7 +1132,6 @@ class CUDT void setInitialSndSeq(int32_t isn) { m_iSndLastAck = isn; - m_iSndLastDataAck = isn; m_iSndLastFullAck = isn; m_iSndCurrSeqNo = CSeqNo::decseq(isn); m_iSndNextSeqNo = isn; @@ -1320,17 +1317,16 @@ class CUDT /// @brief Update sender's loss list on an incoming acknowledgement. /// @param ackdata_seqno sequence number of a data packet being acknowledged - void updateSndLossListOnACK(int32_t ackdata_seqno); + void revokeACKedSequences(int32_t ackdata_seqno); /// Pack a packet from a list of lost packets. /// @param packet [in, out] a packet structure to fill /// @return payload size on success, <=0 on failure int packLostData(CPacket &packet); - std::pair getCleanRexmitOffset(); - bool checkRexmitRightTime(int offset, const sync::steady_clock::time_point& current_time); - int extractCleanRexmitPacket(int32_t seqno, int offset, CPacket& w_packet, - sync::steady_clock::time_point& w_tsOrigin); + int32_t getCleanRexmitOffset(); + bool checkRexmitRightTime(int32_t seqno, const sync::steady_clock::time_point& current_time); + int extractCleanRexmitPacket(int32_t seqno, CPacket& w_packet, sync::steady_clock::time_point& w_tsOrigin); /// Pack a unique data packet (never sent so far) in CPacket for sending. /// @param packet [in, out] a CPacket structure to fill. diff --git a/srtcore/group.cpp b/srtcore/group.cpp index d197a8dbe..a44bc8326 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -3225,7 +3225,7 @@ size_t CUDTGroup::sendBackup_TryActivateStandbyIfNeeded( { CUDT& cudt = d->ps->core(); // Take source rate estimation from an active member (needed for the input rate estimation mode). - cudt.setRateEstimator(w_sendBackupCtx.getRateEstimate()); + cudt.restoreRateEstimator(w_sendBackupCtx.m_rateEstimate); // TODO: At this point all packets that could be sent // are located in m_SenderBuffer. So maybe just use sendBackupRexmit()? @@ -4036,7 +4036,7 @@ int CUDTGroup::sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& w_maxActiveWeight = max(w_maxActiveWeight, d->weight); if (u.m_pSndBuffer) - w_sendBackupCtx.setRateEstimate(u.m_pSndBuffer->getRateEstimator()); + u.m_pSndBuffer->saveEstimation((w_sendBackupCtx.m_rateEstimate)); } else if (erc == SRT_EASYNCSND) { diff --git a/srtcore/group_backup.cpp b/srtcore/group_backup.cpp index bcb0d3ff0..0b927d966 100644 --- a/srtcore/group_backup.cpp +++ b/srtcore/group_backup.cpp @@ -18,6 +18,7 @@ #include #include "group_backup.h" +#include "packet.h" namespace srt @@ -77,6 +78,22 @@ struct FCompareByWeight } }; +SendBackupCtx::SendBackupCtx() + : m_stateCounter() // default init with zeros + , m_activeMaxWeight() + , m_standbyMaxWeight() +{ + // XXX Setting AF_INET6 is a temporary solution for using rate estimator + // that counts a rate based on the current link's IP version. The results + // for links using IPv4 could be slightly falsified due to that (16 bytes + // more per a packet), but this makes the estimation results the same for + // the same data sent over the group, regardless of the IP version used + // for the currently active link (which in reality results in different + // load for the same stream, if links use different IP version). + m_rateEstimate.setHeaderSize(CPacket::HDR_SIZE + CPacket::udpHeaderSize(AF_INET6)); +} + + void SendBackupCtx::recordMemberState(SocketData* pSockData, BackupMemberState st) { m_memberStates.push_back(BackupMemberStateEntry(pSockData, st)); diff --git a/srtcore/group_backup.h b/srtcore/group_backup.h index ed2fb29ff..bfc5a5337 100644 --- a/srtcore/group_backup.h +++ b/srtcore/group_backup.h @@ -75,20 +75,7 @@ namespace groups class SendBackupCtx { public: - SendBackupCtx() - : m_stateCounter() // default init with zeros - , m_activeMaxWeight() - , m_standbyMaxWeight() - // XXX Setting AF_INET6 is a temporary solution for using rate estimator - // that counts a rate based on the current link's IP version. The results - // for links using IPv4 could be slightly falsified due to that (16 bytes - // more per a packet), but this makes the estimation results the same for - // the same data sent over the group, regardless of the IP version used - // for the currently active link (which in reality results in different - // load for the same stream, if links use different IP version). - , m_rateEstimate(AF_INET6) - { - } + SendBackupCtx(); /// @brief Adds or updates a record of the member socket state. /// @param pSocketDataIt Iterator to a socket @@ -118,15 +105,12 @@ namespace groups std::string printMembers() const; - void setRateEstimate(const CRateEstimator& rate) { m_rateEstimate = rate; } - - const CRateEstimator& getRateEstimate() const { return m_rateEstimate; } - private: std::vector m_memberStates; // TODO: consider std::map here? unsigned m_stateCounter[BKUPST_E_SIZE]; uint16_t m_activeMaxWeight; uint16_t m_standbyMaxWeight; + public: CRateEstimator m_rateEstimate; // The rate estimator state of the active link to copy to a backup on activation. }; diff --git a/srtcore/utilities.h b/srtcore/utilities.h index bd1842962..88e701786 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -60,6 +60,21 @@ written by #include "ofmt.h" #include "byte_order.h" +// Maybe not the best place to provide the definition, but it will be also used +// by the utilities defined here. + +#ifdef _DEBUG +#if defined(SRT_ENABLE_THREADCHECK) +#include "threadcheck.h" +#define SRT_ASSERT(cond) ASSERT(cond) +#else +#include +#define SRT_ASSERT(cond) assert(cond) +#endif +#else +#define SRT_ASSERT(cond) +#endif + namespace srt { @@ -219,6 +234,18 @@ class FixedArray public: const T& operator[](Indexer index) const + { + SRT_ASSERT(int(index) < int(m_size)); + return m_entries[int(index)]; + } + + T& operator[](Indexer index) + { + SRT_ASSERT(int(index) < int(m_size)); + return m_entries[int(index)]; + } + + const T& at(Indexer index) const { if (int(index) >= int(m_size)) throw_invalid_index(int(index)); @@ -226,7 +253,7 @@ class FixedArray return m_entries[int(index)]; } - T& operator[](Indexer index) + T& at(Indexer index) { if (int(index) >= int(m_size)) throw_invalid_index(int(index)); @@ -234,9 +261,9 @@ class FixedArray return m_entries[int(index)]; } - size_t size() const { return m_size; } + typedef T value_type; typedef T* iterator; typedef const T* const_iterator; From 1fbe6b495785511082bfbffa93b90fd11160551a Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Fri, 21 Nov 2025 16:17:51 +0100 Subject: [PATCH 10/26] Added alternative sender buffer --- CMakeLists.txt | 5 +- srtcore/buffer_snd.cpp | 1431 +++++++++++++++++++++++++++++++--- srtcore/buffer_snd.h | 349 +++++++-- srtcore/common.h | 56 ++ srtcore/core.cpp | 20 +- srtcore/core.h | 5 +- srtcore/group.cpp | 2 +- srtcore/group.h | 44 -- srtcore/queue.cpp | 5 +- srtcore/utilities.h | 6 + test/filelist.maf | 1 + test/test_buffer_snd.cpp | 238 ++++++ test/test_socket_options.cpp | 44 +- 13 files changed, 1997 insertions(+), 209 deletions(-) create mode 100644 test/test_buffer_snd.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e8407def2..c07053ca0 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1157,7 +1157,7 @@ macro(srt_set_stdcxx targetname spec) set_target_properties(${targetname} PROPERTIES CXX_STANDARD ${stdcxxspec}) #message(STATUS "C++ STD: ${targetname}: forced C++${stdcxxspec} standard - portable way") else() - message(STATUS "APP: ${targetname}: using default C++ standard") + message(STATUS "TARGET: ${targetname}: using default C++ standard") endif() endmacro() @@ -1685,6 +1685,9 @@ if (ENABLE_UNITTESTS) target_include_directories(test-srt PRIVATE ${SSL_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS}) target_compile_definitions(test-srt PRIVATE "-DSRT_TEST_SYSTEM_NAME=\"${CMAKE_SYSTEM_NAME}\"") + # Exceptionally set C++17 standard for tests + srt_set_stdcxx(test-srt 17) + target_link_libraries( test-srt ${GTEST_BOTH_LIBRARIES} diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 95645e696..6a43f28ab 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -57,6 +57,7 @@ modified by #include "buffer_snd.h" #include "packet.h" #include "core.h" // provides some constants +#include "utilities.h" #include "logging.h" namespace srt { @@ -65,53 +66,1247 @@ using namespace std; using namespace srt::logging; using namespace sync; -CSndBuffer::CSndBuffer(size_t bytesize, size_t slicesize, size_t mss, size_t headersize, size_t reservedsize, int flwsize) - : m_BufLock() - , m_iSndLastDataAck(SRT_SEQNO_NONE) - , m_SndLossList(flwsize * 2) - , m_pBlock(NULL) - , m_pFirstBlock(NULL) - , m_pCurrBlock(NULL) - , m_pLastBlock(NULL) - , m_pBuffer(NULL) - , m_iNextMsgNo(1) - , m_iBlockLen(mss - headersize) - , m_iAuthTagSize(reservedsize) - , m_iCount(0) - , m_iBytesCount(0) +CSndBuffer::CSndBuffer(size_t bytesize, size_t slicesize, size_t mss, size_t headersize, size_t reservedsize, int flwsize SRT_ATR_UNUSED) : + m_BufLock(), + m_iBlockLen(mss - headersize), + m_iReservedSize(reservedsize), + m_iSndLastDataAck(SRT_SEQNO_NONE), + m_iNextMsgNo(1), + m_iBytesCount(0), +#if SRT_SNDBUF_NEW + m_iCapacity(number_slices(bytesize, m_iBlockLen)), + m_iSndReservedSeq(SRT_SEQNO_NONE), + // To avoid performance degradation during the transmission, + // we allocate in advance all required blocks so that they are + // picked up from the storage when required. + m_Packets(m_iBlockLen, m_iCapacity) +#else + m_SndLossList(flwsize * 2), + m_pBlock(NULL), + m_pFirstBlock(NULL), + m_pCurrBlock(NULL), + m_pLastBlock(NULL), + m_pFirstMemSlice(NULL), + m_iCount(0) +#endif +{ +#if SRT_SNDBUF_NEW + (void)slicesize; // fake used, but not used +#else + // XXX decide what would be better for the implementation - allocate a single + // slice, allocate all the memory in slices, or just ignore slicesize. + m_iSize = int(slicesize); + (void)bytesize; +#endif + + m_rateEstimator.setHeaderSize(headersize); + + initialize(); + setupMutex(m_BufLock, "Buf"); +} + +#if SRT_SNDBUF_NEW + +void CSndBuffer::initialize() +{ + // Here we can decide, how eagerly the memory can be allocated. +} + +void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) +{ + int32_t& w_msgno = w_mctrl.msgno; + int32_t& w_seqno = w_mctrl.pktseq; + int64_t& w_srctime = w_mctrl.srctime; + const int& ttl = w_mctrl.msgttl; + const int iPktLen = getMaxPacketLen(); + const int iNumBlocks = number_slices(len, iPktLen); + + ScopedLock bufferguard(m_BufLock); + if (m_iSndLastDataAck == SRT_SEQNO_NONE) + m_iSndLastDataAck = w_seqno; + + HLOGC(bslog.Debug, + log << "addBuffer: needs=" << iNumBlocks << " buffers for " << len << " bytes. Taken=" + << m_Packets.size() << "/" << m_iCapacity); + // Retrieve current time before locking the mutex to be closer to packet submission event. + const steady_clock::time_point tnow = steady_clock::now(); + const int32_t inorder = w_mctrl.inorder ? MSGNO_PACKET_INORDER::mask : 0; + + // Calculate origin time (same for all blocks of the message). + m_tsLastOriginTime = w_srctime ? time_point() + microseconds_from(w_srctime) : tnow; + // Rewrite back the actual value, even if it stays the same, so that the calling facilities can reuse it. + // May also be a subject to conversion error, thus the actual value is signalled back. + w_srctime = count_microseconds(m_tsLastOriginTime.time_since_epoch()); + + if (w_msgno == SRT_MSGNO_NONE) // DEFAULT-UNCHANGED msgno supplied + { + HLOGC(bslog.Debug, log << "addBuffer: using internally managed msgno=" << m_iNextMsgNo); + w_msgno = m_iNextMsgNo; + } + else + { + HLOGC(bslog.Debug, log << "addBuffer: OVERWRITTEN by msgno supplied by caller: msgno=" << w_msgno); + m_iNextMsgNo = w_msgno; + } + + for (int i = 0; i < iNumBlocks; ++i) // only 1 normally in live mode + { + int pktlen = len - i * iPktLen; + if (pktlen > iPktLen) + pktlen = iPktLen; + + // This will never fail; it is believed that if the buffer size reached + // defined capacity, addBuffer will not be called. + PacketContainer::Packet& p = m_Packets.push(); + + HLOGC(bslog.Debug, + log << "addBuffer: %" << w_seqno << " #" << w_msgno << " offset=" << (i * iPktLen) + << " size=" << pktlen << " TO BUFFER:" << (void*)p.m_pcData); + memcpy((p.m_pcData), data + i * iPktLen, pktlen); + p.m_iLength = pktlen; + + p.m_iSeqNo = w_seqno; + w_seqno = CSeqNo::incseq(w_seqno); + + p.m_iMsgNoBitset = m_iNextMsgNo | inorder; + if (i == 0) + p.m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST); + if (i == iNumBlocks - 1) + p.m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST); + // NOTE: if i is neither 0 nor size-1, it results with PB_SUBSEQUENT. + // if i == 0 == size-1, it results with PB_SOLO. + // Packets assigned to one message can be: + // [PB_FIRST] [PB_SUBSEQUENT] [PB_SUBSEQUENT] [PB_LAST] - 4 packets per message + // [PB_FIRST] [PB_LAST] - 2 packets per message + // [PB_SOLO] - 1 packet per message + + p.m_iTTL = ttl; + p.m_tsRexmitTime = time_point(); + p.m_tsOriginTime = m_tsLastOriginTime; + } + + m_iBytesCount += len; + + m_rateEstimator.updateInputRate(m_tsLastOriginTime, iNumBlocks, len); + updAvgBufSize(m_tsLastOriginTime); + + const int nextmsgno = ++MsgNo(m_iNextMsgNo); + HLOGC(bslog.Debug, log << "CSndBuffer::addBuffer: updating msgno: #" << m_iNextMsgNo << " -> #" << nextmsgno); + m_iNextMsgNo = nextmsgno; +} + +int CSndBuffer::addBufferFromFile(fstream& ifs, int len) +{ + const int iPktLen = getMaxPacketLen(); + const int iNumBlocks = number_slices(len, iPktLen); + + ScopedLock bufferguard(m_BufLock); + + HLOGC(bslog.Debug, + log << "addBufferFromFile: size=" << m_Packets.size() << " reserved=" << m_iCapacity << " needs=" << iPktLen + << " buffers for " << len << " bytes, msg #" << m_iNextMsgNo); + + int total = 0; + for (int i = 0; i < iNumBlocks; ++i) + { + if (ifs.bad() || ifs.fail() || ifs.eof()) + break; + + int pktlen = len - i * iPktLen; + if (pktlen > iPktLen) + pktlen = iPktLen; + + PacketContainer::Packet& p = m_Packets.push(); + + HLOGC(bslog.Debug, + log << "addBufferFromFile: reading from=" << (i * iPktLen) << " size=" << pktlen + << " TO BUFFER:" << (void*)p.m_pcData); + ifs.read(p.m_pcData, pktlen); + if ((pktlen = int(ifs.gcount())) <= 0) + break; + + // currently file transfer is only available in streaming mode, message is always in order, ttl = infinite + p.m_iMsgNoBitset = m_iNextMsgNo | MSGNO_PACKET_INORDER::mask; + if (i == 0) + p.m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST); + if (i == iNumBlocks - 1) + p.m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST); + // NOTE: PB_FIRST | PB_LAST == PB_SOLO. + // none of PB_FIRST & PB_LAST == PB_SUBSEQUENT. + + p.m_iLength = pktlen; + p.m_iTTL = SRT_MSGTTL_INF; + + total += pktlen; + } + + m_iBytesCount += total; + + m_iNextMsgNo++; + if (m_iNextMsgNo == int32_t(MSGNO_SEQ::mask)) + m_iNextMsgNo = 1; + + return total; +} + +PacketContainer::Packet* PacketContainer::get_unique() +{ + // It should be only possible to be 0, but just in case. + if (m_iNewQueued <= 0) + return NULL; + + SRT_ASSERT(m_iNewQueued <= int(m_Container.size())); + + // If m_iNewQueued == 1, then only the last item is the unique one, + // which's index is size()-1. + size_t index = int(m_Container.size()) - m_iNewQueued; + --m_iNewQueued; // We checked in advance that it's > 0. + return &m_Container[index]; +} + +int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc) +{ + int readlen = 0; + w_seqnoinc = 0; + ScopedLock bufferguard(m_BufLock); + + // REPEATABLE BLOCK + // In the block there will be skipped the TTL-expired messages, if any. + for (;;) + { + PacketContainer::Packet* p = m_Packets.get_unique(); + if (!p) + return 0; + + if ((p->m_iTTL >= 0) && (count_milliseconds(steady_clock::now() - w_srctime) > p->m_iTTL)) + { + // Skip this packet due to TTL expiry. + // Note: the packet is no longer unique, even though it was never sent. + LOGC(bslog.Warn, log << CONID() << "CSndBuffer: skipping packet %" << p->m_iSeqNo << " #" << p->getMsgSeq() + << " with TTL=" << p->m_iTTL); + + // Just in case, but unique packets should have this field always 0. + p->m_tsNextRexmitTime = time_point(); + + readlen = 0; + ++w_seqnoinc; + continue; + } + + // Make the packet REFLECT the data stored in the buffer. + w_packet.m_pcData = p->m_pcData; + readlen = p->m_iLength; + w_packet.setLength(readlen, m_iBlockLen); + w_packet.set_seqno(p->m_iSeqNo); + + // 1. On submission (addBuffer), the KK flag is set to EK_NOENC (0). + // 2. The readData() is called to get the original (unique) payload not ever sent yet. + // The payload must be encrypted for the first time if the encryption + // is enabled (arg kflgs != EK_NOENC). The KK encryption flag of the data packet + // header must be set and remembered accordingly (see EncryptionKeySpec). + // 3. The next time this packet is read (only for retransmission), the payload is already + // encrypted, and the proper flag value is already stored. + + // TODO: Alternatively, encryption could happen before the packet is submitted to the buffer + // (before the addBuffer() call), and corresponding flags could be set accordingly. + // This may also put an encryption burden on the application thread, rather than the sending thread, + // which could be more efficient. Note that packet sequence number must be properly set in that case, + // as it is used as a counter for the AES encryption. + if (kflgs == -1) + { + HLOGC(bslog.Debug, log << CONID() << " CSndBuffer: ERROR: encryption required and not possible. NOT SENDING."); + readlen = 0; + } + else + { + p->m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(kflgs); + } + + // Reserve this seqno to persist for the time when this packet is used + // for being sent, until it's released. + if (m_iSndReservedSeq == SRT_SEQNO_NONE || SeqNo(p->m_iSeqNo) < SeqNo(m_iSndReservedSeq)) + { + m_iSndReservedSeq = p->m_iSeqNo; + } + + HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: UNIQUE packet to send: size=" << readlen + << " #" << w_packet.getMsgSeq() + << " %" << w_packet.seqno() + << " !" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); + + break; + } + + return readlen; +} + +CSndBuffer::time_point CSndBuffer::peekNextOriginal() const +{ + ScopedLock bufferguard(m_BufLock); + + // Use unique_size() because we want to access the next unique + // packet without removing it from the unique range. + if (m_Packets.unique_size() == 0) + return time_point(); + + size_t ux = int(m_Packets.size()) - m_Packets.unique_size(); + return m_Packets[ux].m_tsOriginTime; +} + +int32_t CSndBuffer::getMsgNoAtSeq(const int32_t seqno) +{ + ScopedLock bufferguard(m_BufLock); + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + + if (offset < 0 || offset >= int(m_Packets.size())) + { + // Prevent accessing the last "marker" block + LOGC(bslog.Error, + log << "CSndBuffer::getMsgNoAtSeq: IPE: for %" << seqno << " offset=" << offset + << " outside container; max offset=" << m_Packets.size()); + return SRT_MSGNO_CONTROL; + } + + return m_Packets[offset].getMsgSeq(); +} + +int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) +{ + ScopedLock bufferguard(m_BufLock); + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + if (offset < 0 || offset >= int(m_Packets.size())) + { + LOGC(qslog.Error, log << "CSndBuffer::readOldPacket: for %" << seqno << " offset " << offset << " out of buffer (earliest: %" + << m_iSndLastDataAck << ")!"); + return READ_NONE; + } + + // Unlike receiver buffer, in the sender buffer packets are always stored + // one after another and there are no gaps. Checking the valid range of offset + // suffices to grant existence of a packet. + PacketContainer::Packet* p = &m_Packets[offset]; + +#if HVU_ENABLE_HEAVY_LOGGING + const int32_t first_seq = p->m_iSeqNo; + int32_t last_seq = p->m_iSeqNo; +#endif + + w_packet.set_seqno(seqno); + + // This is rexmit request, so the packet should have the sequence number + // already set when it was once sent uniquely. + SRT_ASSERT(p->m_iSeqNo == w_packet.seqno()); + + // Check if the block that is the next candidate to send (m_pCurrBlock pointing) is stale. + + if ((p->m_iTTL >= 0) && (count_milliseconds(steady_clock::now() - p->m_tsOriginTime) > p->m_iTTL)) + { + int same_msgno = p->getMsgSeq(); + + int lastx = offset; + + // This loop may run also 0 times if we have one message per packet. + // Note that the API theoretically allows scheduling data to the buffer + // multiple times with the same message number, although you'd have to + // enforce it in every call, and each case with a different TTL. That's + // for the responsibility of the user. + for (int i = offset+1; i < int(m_Packets.size()); ++i) + { + if (m_Packets[i].getMsgSeq() != same_msgno) + break; + + // In the meantime, as it goes, revoke it from the retransmission schedule + m_Packets[i].m_tsNextRexmitTime = time_point(); + + lastx = i; + } + + HLOGC(qslog.Debug, + log << "CSndBuffer::readData: due to TTL exceeded, %(" << first_seq << " - " << last_seq << "), " + << (1 + lastx - offset) << " packets to drop with #" << w_drop.msgno); + + // Make sure that the packets belonging to the expired message are + // no longer in the unique range, even if they were before. + m_Packets.set_expired(lastx); + w_drop.msgno = p->getMsgSeq(); + + w_drop.seqno[DropRange::BEGIN] = w_packet.seqno(); + w_drop.seqno[DropRange::END] = CSeqNo::incseq(w_packet.seqno(), lastx - offset); + + // We let the caller handle it, while we state no packet delivered. + // NOTE: the expiration of a message doesn't imply recovation from the buffer. + // Revocation will still happen on ACK. + return READ_DROP; + } + + w_packet.m_pcData = p->m_pcData; + const int readlen = p->m_iLength; + w_packet.setLength(readlen, m_iBlockLen); + + // We state that the requested seqno refers to a historical (not unique) + // packet, hence the encryption action has encrypted the data and updated + // the flags. + w_packet.set_msgflags(p->m_iMsgNoBitset); + w_srctime = p->m_tsOriginTime; + + // This function is called when packet retransmission is triggered. + // Therefore we are setting the rexmit time. + p->m_tsRexmitTime = steady_clock::now(); + + // Reserve this seqno to persist for the time when this packet is used + // for being sent, until it's released. + if (m_iSndReservedSeq == SRT_SEQNO_NONE || SeqNo(p->m_iSeqNo) < SeqNo(m_iSndReservedSeq)) + { + m_iSndReservedSeq = p->m_iSeqNo; + } + + HLOGC(qslog.Debug, + log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.seqno() + << " size=" << readlen << " to send [REXMIT]"); + + return readlen; +} + +sync::steady_clock::time_point CSndBuffer::getRexmitTime(const int32_t seqno) +{ + ScopedLock bufferguard(m_BufLock); + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + if (offset < 0 || offset >= int(m_Packets.size())) + return sync::steady_clock::time_point(); + + return m_Packets[offset].m_tsRexmitTime; +} + +bool CSndBuffer::reserveSeqno(int32_t seq) +{ + ScopedLock bufferguard(m_BufLock); + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); + + // IF distance between m_iSndLastDataAck and ack is nonempty... + if (offset < 0 || offset >= int(m_Packets.size())) + return false; + + // If there exists any previous reservation, do not + // overwrite it. + if (m_iSndReservedSeq != SRT_SEQNO_NONE && SeqNo(m_iSndReservedSeq) < SeqNo(seq)) + return true; + + m_iSndReservedSeq = seq; + return true; +} + +bool CSndBuffer::releaseSeqno(int32_t new_ack_seq) +{ + ScopedLock bufferguard(m_BufLock); + if (m_iSndReservedSeq == SRT_SEQNO_NONE) + { + // We state there is no reservation, so m_iSndLastDataAck == new_ack_seq. + return false; + } + + // Unreserve, regardless of the value of new_ack_seq. We need that + // since this moment the buffer can be freely revoked by ACK. + m_iSndReservedSeq = SRT_SEQNO_NONE; + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, new_ack_seq); + if (offset <= 0) // new_ack_seq doesn't succeed any packets in the buffer + return false; + + // Now continue with revoke. Not calling revoke() due to + // mutex complications. + m_Packets.pop(offset); + + m_iSndLastDataAck = new_ack_seq; + + updAvgBufSize(steady_clock::now()); + return true; +} + +bool CSndBuffer::revoke(int32_t seqno) +{ + ScopedLock bufferguard(m_BufLock); + + // If there exists reservation marker, check if this is going to be + // released. If so, keep all packets up to the reserved position. + if (m_iSndReservedSeq != SRT_SEQNO_NONE && SeqNo(seqno) > SeqNo(m_iSndReservedSeq)) + { + seqno = m_iSndReservedSeq; + } + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + + // IF distance between m_iSndLastDataAck and ack is nonempty... + if (offset <= 0) + return false; + + // NOTE: offset points to the first packet that should remain + // in the buffer, hence it's already a past-the-end for the revoked. + // The call is also safe for calling with excessive value of offset. + m_Packets.pop(offset); + + // We don't check if the sequence is past the last scheduled one; + // worst case scenario we'll just clear up the whole buffer. + m_iSndLastDataAck = seqno; + + updAvgBufSize(steady_clock::now()); + return true; +} + +// NOTE: 'n' is the index in the m_Container array +// up to which (including) the losses must be cleared off. +// This should only result in making the m_iFirstRexmit +// and m_iLastRexmit field pointing to either -1 or +// valid indexes in the container, but OUTSIDE the range +// from 0 to n. +void PacketContainer::remove_loss(int last_to_clear) +{ + // This is going to remove the loss records since the first one + // up to the packet designated by the last_to_clear offset (same as pop()). + + // this empty() is just formally - with empty m_iFirstRexmit should be moreover -1 + if (m_iFirstRexmit == -1 || m_Container.empty()) // no loss records + return; // last is also -1 in this situation + + const int LASTX = int(m_Container.size()) - 1; + + // Handle special case: if last_to_clear is the last index in the container, + // simply remove everything. Just update all the loss nodes. + if (last_to_clear >= LASTX) + { + for (int loss = m_iFirstRexmit, next; loss != -1; loss = next) + { + // use safe-loop rule because the node data will be cleared here. + next = next_loss(loss); + PacketContainer::Packet& p = m_Container[loss]; + p.m_iLossLength = 0; + p.m_iNextLossGroupOffset = 0; + } + m_iFirstRexmit = -1; + m_iLastRexmit= -1; + m_iLossLengthCache = 0; + return; + } + + // The iteration rule here: + // Make calculations on the indexes with unchanged relative values. + // That is, just clear the records that point to a less index than last_to_clear. + // Values of m_iFirstRexmit and m_iLastRexmit still refer to the unchanged + // indexes in the container, just must be outside the removed region. + int removed_loss_length = 0; + int first_to_clear = -1; + for (;;) + { + if (last_to_clear < m_iFirstRexmit) + { + // Found at THIS RECORD (possibly having dismissed earlier ones) + // that it's already in the non-revoked region. + + // Update with the length of every loss record removed in this loop. + m_iLossLengthCache = m_iLossLengthCache - removed_loss_length; + // That's it, nothing more to do. + break; + } + + if (first_to_clear == -1) + first_to_clear = m_iFirstRexmit; + + // Ride until you find a split-in-half record, + // a new record beyond last_to_clear, or no more records. + PacketContainer::Packet& p = m_Container[m_iFirstRexmit]; + + int last_index = m_iFirstRexmit + p.m_iLossLength - 1; + if (last_to_clear < last_index) + { + // split-in-half case. This will be the last on which the operation is done. + + int new_beginning = last_to_clear + 1; // The case when last_to_clear == m_Container.size() - 1 is handled already + + int revoked_length_fragment = new_beginning - m_iFirstRexmit; + + // Now shift the position + bool is_last = false; + m_Container[new_beginning].m_iLossLength = p.m_iLossLength - revoked_length_fragment; + if (p.m_iNextLossGroupOffset) + { + int next_index = m_iFirstRexmit + p.m_iNextLossGroupOffset; + // Replicate the distance at the new index + m_Container[new_beginning].m_iNextLossGroupOffset = next_index - new_beginning; + } + else + { + // No next group, this is the last one. + m_Container[new_beginning].m_iNextLossGroupOffset = 0; + is_last = true; + } + + // Cancel the previous first node + p.m_iLossLength = 0; + p.m_iNextLossGroupOffset = 0; + + // NOTE: the new values of m_iFirstRexmit and m_iLastRexmit set here + // are valid indexes AFTER REMOVAL of the revoked elements from m_Container. + m_iFirstRexmit = new_beginning; + if (is_last) + m_iLastRexmit = m_iFirstRexmit; + // If not last, there is some record next to first which remains last. + + // Removed were all previous completely skipped record before length last_to_clear, + // plus a fragment of the record that was split in half. + m_iLossLengthCache = m_iLossLengthCache - removed_loss_length - revoked_length_fragment; + + break; + } + + // Check if this one was the last record; if so, we have cleared all. + if (p.m_iNextLossGroupOffset == 0) + { + p.m_iLossLength = 0; + m_iFirstRexmit = -1; + m_iLastRexmit = -1; + m_iLossLengthCache = 0; + break; + } + + // Remaining case: the whole record is below last_to_clear (so remove it and try next) + // Remove means that you need to clear this packet from being a hook of + // a loss record, and move m_iFirstRexmit to the next record. + removed_loss_length += p.m_iLossLength; + m_iFirstRexmit += p.m_iNextLossGroupOffset; + + p.m_iLossLength = 0; + p.m_iNextLossGroupOffset = 0; + } +} + +bool CSndBuffer::cancelLostSeq(int32_t seq) +{ + ScopedLock bufferguard(m_BufLock); + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); + if (offset < 0 || offset >= int(m_Packets.size())) + return false; + + return m_Packets.clear_loss(offset); +} + +bool PacketContainer::clear_loss(int index) +{ + // Just access the record. Now return false only + // if it turns out that it has never been rexmit-scheduled. + PacketContainer::Packet& p = m_Container[index]; + if (is_zero(p.m_tsNextRexmitTime)) + return false; + + p.m_tsNextRexmitTime = time_point(); + return true; +} + +void PacketContainer::clear() +{ + // pop() will do erase(begin(), end()) in this case, + // but will also destroy every packet. + pop(size()); +} + +// 'n' is the past-the-end index for removal +size_t PacketContainer::pop(size_t n) +{ + if (m_Container.empty() || !n) + return 0; // The size is also unchanged + + if (n > m_Container.size()) + n = m_Container.size(); + + // We consider that this call clears off losses in the container + // calls from 0 to n (inc). + remove_loss(n-1); // remove_loss includes given index + + deque::iterator i = m_Container.begin(), upto = i + n; + for (; i != upto; ++i) + { + // Deallocate storage + m_Storage.put(i->m_pcData); + } + + m_Container.erase(m_Container.begin(), upto); + m_iCachedSize = m_Container.size(); + + // pop might have removed also packets from the unique range; + // in that case just shrink it to the existing range. + m_iNewQueued = std::min(m_iNewQueued, m_Container.size()); + + // These are indexes into the m_Container container, so with + // removed n elements, their position in the container also + // get shifted by n. + if (m_iFirstRexmit != -1) + { + // After remove_loss(), these indexes were updated so that they + // do not (*should* not) refer to any elements earlier than n. + SRT_ASSERT(m_iFirstRexmit >= int(n)); + SRT_ASSERT(m_iLastRexmit >= m_iFirstRexmit); + + m_iFirstRexmit -= n; + m_iLastRexmit -= n; + } + + return n; +} + +bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point& next_rexmit_time) +{ + // Can't install loss to an empty container + if (m_Container.empty()) + return false; + + // Fix the indexes if they are out of bound. Note that they can potentially + // be very far from the point, but any rollovers should be ignored (there's + // not much we can do about it). Check only if this really is lo-hi relationship + // and whether at least a fragment of the range is in the buffer. + + if (offset_lo > offset_hi || offset_hi < 0 || offset_lo >= int(m_Container.size())) + return false; + + if (offset_lo < 0) + { + //int fix = 0 - offset_lo; + offset_lo = 0; + //seqlo = CSeqNo::incseq(seqlo, fix); + } + + // It was checked that size() is at least 1 + if (offset_hi >= int(m_Container.size())) + { + //int fix = offset_hi - m_Container.size(); + offset_hi = m_Container.size() - 1; + //seqhi = CSeqNo::decseq(seqhi, fix); + } + + int loss_length = offset_hi - offset_lo + 1; + + // Ok, check now where the position is towards the + // existing records. + // + // First: check if there are no records yet. + if (m_iFirstRexmit == -1) + { + // Add just one record and mark in both. + PacketContainer::Packet& p = m_Container[offset_lo]; + p.m_iNextLossGroupOffset = 0; + p.m_iLossLength = loss_length; + m_iFirstRexmit = m_iLastRexmit = offset_lo; + + m_iLossLengthCache = loss_length; + set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + return true; + } + + // Check relationship with the last one. If past the last one, + // simply pin in the new one. + if (offset_lo >= m_iLastRexmit) + { + // This means, it's eaither overlapping with m_iLastRexmit, + // or past the last record. What we know for sure is that it + // doesn't overlap with any earlier loss records. + + PacketContainer::Packet& butlast = m_Container[m_iLastRexmit]; + + // Still, the record can overlap. + int last_end_ix = m_iLastRexmit + butlast.m_iLossLength; // past-the-end! + + // If the next to insert is exactly next to the last record, + // only extend the existing last record + if (last_end_ix >= offset_lo) + { + // Overlap. Update the loss length, all else remains untouched. + // old_length defines the fragment that will be wiped due to being + // merged with the current one. + int old_length = butlast.m_iLossLength; + int new_length = offset_hi - m_iLastRexmit + 1; + butlast.m_iLossLength = new_length; + + m_iLossLengthCache = m_iLossLengthCache + new_length - old_length; + set_rexmit_time(m_iLastRexmit, offset_hi, next_rexmit_time); + return true; + } + + // No overlaps, just add the new last one. + PacketContainer::Packet& last = m_Container[offset_lo]; + + int butlast_distance = offset_lo - m_iLastRexmit; + + // The length of the last record remains unchanged, + // just the next-pointer needs to be set. + butlast.m_iNextLossGroupOffset = butlast_distance; + + int new_length = offset_hi - offset_lo + 1; + m_iLastRexmit = offset_lo; + last.m_iNextLossGroupOffset = 0; // this is now the last one + last.m_iLossLength = new_length; + m_iLossLengthCache = m_iLossLengthCache + new_length; + set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + return true; + } + + // Ruled-out case: past-the-last insertion or overlap with the last record. + // All other cases are: + // - preceding the first + // - overlapping or interleaving with existing records, EXCEPT the last one. + // (still may overlap with the last record, that is, extend its beginning) + + // --- PacketContainer::Packet& p = m_Container[m_iFirstRexmit]; + vector index_to_clear; + + // Current form: + // [FIRST...END] [FURTHER1...END] [FURTHER2...END]... [LAST...END] + // | | \ | + // \ \ [B] + // [A] [C] + // Cases: + // [A] Very first (preceding the first record and not overlapping with it) + // [B] intermixed or overlapped with existing records + // Then the mix of two conditions: + // a.: + // 1. Intermixed with existing + // 2. Overlapping with existing + + // This determines the index of the first record that we will + // consider as intermixing or overlapping. + + // --- int loss_index_end = m_iFirstRexmit + p.m_iLossLength; + // --- SRT_ASSERT(loss_index_end > m_iFirstRexmit); + + int lowest_lo = m_iFirstRexmit; + int last_preceding_index = -1; + + if (offset_lo < m_iFirstRexmit) + { + // Detemine and handle case A. This doesn't require any modification + // of existing records, just inserting one as the first, and the sofar + // first will be its next. + if (offset_hi < m_iFirstRexmit) + { + PacketContainer::Packet& first = m_Container[offset_lo]; + first.m_iLossLength = loss_length; + first.m_iNextLossGroupOffset = m_iFirstRexmit - offset_lo; + m_iFirstRexmit = offset_lo; + m_iLossLengthCache = m_iLossLengthCache + loss_length; + set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + return true; + } + // Otherwise continue with first potentially overlapping + // or preceding. + } + // We use else because if the lowest index precedes the first, + // there couldn't exist a case that any whole records precede it. + else + { + // As the lower range is already past the begin, + // skip all records that whole precede the inserted record. + for (int loss_index = m_iFirstRexmit; loss_index != -1; loss_index = next_loss(loss_index)) + { + PacketContainer::Packet& ip = m_Container[loss_index]; + int loss_end = loss_index + ip.m_iLossLength; + + if (loss_end >= offset_lo) + { + lowest_lo = loss_index; + break; + } + + // If this value remains -1, it means that the record to + // insert overlaps with the very first record. Otherwise + // there will always be some records preceding and this + // needs to be linked with the prospective next one. + last_preceding_index = loss_index; + } + } + + // Now the situation is: + // [potentially skipped preceding...] | [LOWEST_LO...END] [FURTHER1...END] [FURTHER2...END]... [LAST...END] + // + // Check now if there is a record that directly succeeds the inserted one. + int next_succeeding = 0; // Default value to mark m_iNextLossGroupOffset, which is "no next record". + + int eclipsed_length = 0; // This is to collect length of all removed records to be re-added with this one. + for (int loss_index = lowest_lo; loss_index != -1; loss_index = next_loss(loss_index)) + { + // We search for the first non-overlapping one. + if (loss_index > offset_hi) + { + next_succeeding = loss_index; + break; + } + + // All indices in the middle should be cleared + index_to_clear.push_back(loss_index); + eclipsed_length += m_Container[loss_index].m_iLossLength; + } + + // Now its: + // [preceding...] | [LOWEST_LO...END] [FURTHER1...END] [FURTHER2...END]... | [NEXT_SUCCEEDING...END]... + // or + // [preceding...] | [LOWEST_LO...END] [FURTHER1...END] [FURTHER2...END]... [LAST...END] + // + // next_succeeding is only the value to set to m_iNextLossGroupOffset. + // Length is determined by earliest index min(offset_lo, lowest_lo) and max(offset_hi, last_hi+length-1) + + + // Now clear everything in the records "in between", except that catch + // the farthest index ever seen in this range. + int furthest_hi = offset_hi; + for (size_t i = 0; i < index_to_clear.size(); ++i) + { + int x = index_to_clear[i]; + int hi = x + m_Container[x].m_iLossLength - 1; + m_Container[x].m_iLossLength = 0; + m_Container[x].m_iNextLossGroupOffset = 0; + furthest_hi = std::max(furthest_hi, hi); + } + + if (lowest_lo != offset_lo) + { + // Clear the packet if it wasn't at the border + m_Container[lowest_lo].m_iNextLossGroupOffset = 0; + m_Container[lowest_lo].m_iLossLength = 0; + } + else + { + lowest_lo = min(lowest_lo, offset_lo); + } + loss_length = furthest_hi - lowest_lo + 1; + + m_Container[lowest_lo].m_iLossLength = loss_length; + m_Container[lowest_lo].m_iNextLossGroupOffset = next_succeeding; + + int new_length = m_iLossLengthCache - eclipsed_length + loss_length; + + if (last_preceding_index == -1) + m_iFirstRexmit = lowest_lo; + else + m_Container[last_preceding_index].m_iNextLossGroupOffset = lowest_lo; + m_iLossLengthCache = new_length; + + // Set the rexmit time only to the range that was requested to be inserted, + // even if this is effectively a fragment of a record. + set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + return true; +} + +int32_t CSndBuffer::popLostSeq(DropRange& w_drop) { - // XXX decide what would be better for the implementation - allocate a single - // slice, allocate all the memory in slices, or just ignore slicesize. - m_iSize = std::min(int(slicesize), countNumPacketsRequired(bytesize, m_iBlockLen)); + static const DropRange nodrop = { {SRT_SEQNO_NONE, SRT_SEQNO_NONE}, SRT_MSGNO_CONTROL }; + w_drop = nodrop; - m_rateEstimator.setHeaderSize(headersize); + ScopedLock bufferguard(m_BufLock); - initialize(); - setupMutex(m_BufLock, "Buf"); + // In this version we don't predict any drop requests; + // the sequence is taken directly from the sender buffer, so there's + // physically no possibility to have any sequence lost that is not + // among the packets in the buffer. + + // Ok, this is our sequence to report. + int i = m_Packets.extractFirstLoss(); + if (i == -1) + return SRT_SEQNO_NONE; + + int32_t seqno = CSeqNo::incseq(m_iSndLastDataAck, i); + return seqno; } -CSndBuffer::~CSndBuffer() +int PacketContainer::extractFirstLoss() { - CSndBlock* pb = m_pBlock->m_pNext; - while (pb != m_pBlock) + // No loss at all + if (m_iFirstRexmit == -1) + return -1; + + // Theoretically you can take the cell at m_iFirstRexmit and revoke it, + // but the record could have been rexmit-cleared, in which case you should + // skip it, and try on the next one. All records skipped this way must be + // revoked together with the first found valid loss. + + // However, you also have this time to check against now, so if + // this time is in the future, the record must stay there; you can + // still pick up any next cell, but you can't revoke anything, + // except those preceding the "future rexmit" cell. + int stop_revoke = -1; + + time_point now = steady_clock::now(); + + // Walk over the container to find the valid loss sequence + for (int loss_begin = m_iFirstRexmit; loss_begin != -1; loss_begin = next_loss(loss_begin)) { - CSndBlock* temp = pb; - pb = pb->m_pNext; - delete temp; + int loss_end = loss_begin + m_Container[loss_begin].m_iLossLength; + + for (int i = loss_begin; i != loss_end; ++i) + { + PacketContainer::Packet& p = m_Container[i]; + if (!is_zero(p.m_tsNextRexmitTime)) + { + // Ok, so this cell will be taken, but it might be the future. + if (p.m_tsNextRexmitTime > now) + { + if (stop_revoke == -1 && i > 0) + stop_revoke = i - 1; + continue; + } + + + // Clear that packet from being rexmit-eligible. + p.m_tsNextRexmitTime = time_point(); + + if (stop_revoke == -1) + remove_loss(i); // Remove all previous loss records, including this one + else + remove_loss(stop_revoke); + return i; + } + // If it was cleared, continue searching. + } } - delete m_pBlock; - while (m_pBuffer != NULL) + return -1; +} + +void CSndBuffer::removeLossUpTo(int32_t seqno) +{ + ScopedLock bufferguard(m_BufLock); + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + + if (offset < 0 || offset >= int(m_Packets.size())) + return; + + m_Packets.remove_loss(offset); +} + +int CSndBuffer::insertLoss(int32_t seqlo, int32_t seqhi, const time_point& pt) +{ + ScopedLock bufferguard(m_BufLock); + int offset_lo = CSeqNo::seqoff(m_iSndLastDataAck, seqlo); + int offset_hi = CSeqNo::seqoff(m_iSndLastDataAck, seqhi); + + return m_Packets.insert_loss(offset_lo, offset_hi, is_zero(pt) ? steady_clock::now(): pt); +} + +int CSndBuffer::getLossLength() +{ + return m_Packets.loss_length(); +} + +int CSndBuffer::getCurrBufSize() const +{ + return m_Packets.size(); +} + +int CSndBuffer::getMaxPacketLen() const +{ + return m_iBlockLen - m_iReservedSize; +} + +int CSndBuffer::countNumPacketsRequired(int iPldLen) const +{ + const int iPktLen = getMaxPacketLen(); + return number_slices(iPldLen, iPktLen); +} + +int CSndBuffer::getAvgBufSize(int& w_bytes, int& w_tsp) +{ + ScopedLock bufferguard(m_BufLock); /* Consistency of pkts vs. bytes vs. spantime */ + + /* update stats in case there was no add/ack activity lately */ + updAvgBufSize(steady_clock::now()); + + // Average number of packets and timespan could be small, + // so rounding is beneficial, while for the number of + // bytes in the buffer is a higher value, so rounding can be omitted, + // but probably better to round all three values. + w_bytes = m_mavg.bytes() + 0.49; + w_tsp = m_mavg.timespan_ms() + 0.49; + return int(m_mavg.pkts() + 0.49); +} + +void CSndBuffer::updAvgBufSize(const steady_clock::time_point& now) +{ + if (!m_mavg.isTimeToUpdate(now)) + return; + + int bytes = 0; + int timespan_ms = 0; + const int pkts = getBufferStats((bytes), (timespan_ms)); + m_mavg.update(now, pkts, bytes, timespan_ms); +} + +int CSndBuffer::getBufferStats(int& w_bytes, int& w_timespan) const +{ + w_bytes = m_iBytesCount; + /* + * Timespan can be less then 1000 us (1 ms) if few packets. + * Also, if there is only one pkt in buffer, the time difference will be 0. + * Therefore, always add 1 ms if not empty. + */ + if (m_Packets.empty()) + w_timespan = 0; + else + w_timespan = count_milliseconds(m_tsLastOriginTime - m_Packets[0].m_tsOriginTime) + 1; + + + return m_Packets.size(); +} + +CSndBuffer::duration CSndBuffer::getBufferingDelay(const time_point& tnow) const +{ + ScopedLock lck(m_BufLock); + + if (m_Packets.empty()) + return duration(0); + + return tnow - m_Packets[0].m_tsOriginTime; +} + +int CSndBuffer::dropLateData(int& w_bytes, int32_t& w_first_msgno, const steady_clock::time_point& too_late_time) +{ + ScopedLock bufferguard(m_BufLock); + + int dbytes = 0; + int32_t msgno = 0; + // Reach out to the position that is less than too_late_time, + // counting the bytes + size_t i; + for (i = 0; i < m_Packets.size() && m_Packets[i].m_tsOriginTime < too_late_time; ++i) { - Buffer* temp = m_pBuffer; - m_pBuffer = m_pBuffer->m_pNext; - delete[] temp->m_pcData; - delete temp; + dbytes += m_Packets[i].m_iLength; + msgno = m_Packets[i].getMsgSeq(); + } + + // Now delete all these packets from the container + if (i) + { + m_Packets.pop(i); + + const int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, i); + m_iSndLastDataAck = fakeack; } + w_bytes = dbytes; // even if 0 + + // We report the increased number towards the last ever seen + // by the loop, as this last one is the last received. So remained + // (even if "should remain") is the first after the last removed one. + w_first_msgno = ++MsgNo(msgno); + + updAvgBufSize(steady_clock::now()); + + return i; +} + +int CSndBuffer::dropAll(int& w_bytes) +{ + ScopedLock bufferguard(m_BufLock); + // clear all + + int dpkts = m_Packets.size(); + m_Packets.clear(); + w_bytes = m_iBytesCount; + m_iBytesCount = 0; + + updAvgBufSize(steady_clock::now()); + return dpkts; +} + +CSndBuffer::~CSndBuffer() +{ releaseMutex(m_BufLock); } +string CSndBuffer::show() const +{ + using namespace hvu; + ofmtbufstream out; + + int minw = 2; + if (m_Packets.size() > 99) + minw = 3; + else if (m_Packets.size() > 999) + minw = 4; + + fmtc findex = fmtc().width(minw).fillzero(); + + ScopedLock bufferguard(m_BufLock); + + PacketContainer::PacketShowState st; + for (size_t i = 0; i < m_Packets.size(); ++i) + { + int seqno = CSeqNo::incseq(m_iSndLastDataAck, i); + out << "[" << fmt(i, findex) << "]%" << seqno << ": "; + m_Packets.showline(i, (st), (out)); + out.puts(); + } + + return out.str(); +} + +void PacketContainer::showline(int index, PacketShowState& st, hvu::ofmtbufstream& out) const +{ + const Packet& p = m_Container[index]; + + if (is_zero(st.begin_time)) + st.begin_time = steady_clock::now(); + + out << p.m_iLength << "!" << BufferStamp(p.m_pcData, p.m_iLength); + + // Check beginning of the new loss group + if (index == m_iFirstRexmit) + { + if (st.remain_loss_group || st.next_loss_begin != -1) + out << " *** UNEXPECTED rem=" << st.remain_loss_group << " next=" << st.next_loss_begin << " at first=" << m_iFirstRexmit; + + // Configure context object + st.remain_loss_group = p.m_iLossLength; + st.next_loss_begin = p.m_iNextLossGroupOffset ? index + p.m_iNextLossGroupOffset : -1; + if (st.remain_loss_group == 0) + out << " *** UNEXPECTED index=" << index << " marked next, but length=0!"; + } + else if (index == st.next_loss_begin) + { + if (st.remain_loss_group) + out << " *** UNEXPECTED rem=" << st.remain_loss_group << " next=" << st.next_loss_begin << " at first=" << m_iFirstRexmit; + + // Configure context object + st.remain_loss_group = p.m_iLossLength; + st.next_loss_begin = p.m_iNextLossGroupOffset ? index + p.m_iNextLossGroupOffset : -1; + if (st.remain_loss_group == 0) + out << " *** UNEXPECTED index=" << index << " marked next, but length=0!"; + } + else + { + if (p.m_iLossLength || p.m_iNextLossGroupOffset) + out << " *** UNEXPECTED subseq loss-len=" << p.m_iLossLength << " next=" << p.m_iNextLossGroupOffset; + } + + if (st.remain_loss_group) + { + out << " L."; + if (is_zero(p.m_tsNextRexmitTime)) + out << "0"; + else + out << FormatDurationAuto(st.begin_time - p.m_tsNextRexmitTime); + + out << "/" << st.remain_loss_group; + + --st.remain_loss_group; + } + + int queued_range_begin = size() - m_iNewQueued; + if (index >= queued_range_begin) + { + out << " NEW"; + } +} + +#else + void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) { int32_t& w_msgno = w_mctrl.msgno; @@ -119,7 +1314,7 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) int64_t& w_srctime = w_mctrl.srctime; const int& ttl = w_mctrl.msgttl; const int iPktLen = getMaxPacketLen(); - const int iNumBlocks = countNumPacketsRequired(len, iPktLen); + const int iNumBlocks = number_slices(len, iPktLen); if (m_iSndLastDataAck == SRT_SEQNO_NONE) m_iSndLastDataAck = w_seqno; @@ -154,7 +1349,7 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) // If there's more than one packet, this function must increase it by itself // and then return the accordingly modified sequence number in the reference. - CSndBlock* s = m_pLastBlock; + Block* s = m_pLastBlock; if (w_msgno == SRT_MSGNO_NONE) // DEFAULT-UNCHANGED msgno supplied { @@ -224,11 +1419,11 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) int CSndBuffer::addBufferFromFile(fstream& ifs, int len) { const int iPktLen = getMaxPacketLen(); - const int iNumBlocks = countNumPacketsRequired(len, iPktLen); + const int iNumBlocks = number_slices(len, iPktLen); HLOGC(bslog.Debug, log << "addBufferFromFile: size=" << m_iCount.load() << " reserved=" << m_iSize << " needs=" << iPktLen - << " buffers for " << len << " bytes"); + << " buffers for " << len << " bytes, msg #" << m_iNextMsgNo); // dynamically increase sender buffer while (iNumBlocks + m_iCount >= m_iSize) @@ -238,11 +1433,7 @@ int CSndBuffer::addBufferFromFile(fstream& ifs, int len) increase(); } - HLOGC(bslog.Debug, - log << CONID() << "addBufferFromFile: adding " << iPktLen << " packets (" << len - << " bytes) to send, msgno=" << m_iNextMsgNo); - - CSndBlock* s = m_pLastBlock; + Block* s = m_pLastBlock; int total = 0; for (int i = 0; i < iNumBlocks; ++i) { @@ -327,7 +1518,7 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, m_pCurrBlock->m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(kflgs); } - CSndBlock* p = m_pCurrBlock; + Block* p = m_pCurrBlock; w_packet.set_msgflags(m_pCurrBlock->m_iMsgNoBitset); w_srctime = m_pCurrBlock->m_tsOriginTime; m_pCurrBlock = m_pCurrBlock->m_pNext; @@ -375,7 +1566,7 @@ int32_t CSndBuffer::getMsgNoAtSeq(const int32_t seqno) return SRT_MSGNO_CONTROL; } - CSndBlock* p = m_pFirstBlock; + Block* p = m_pFirstBlock; if (p) { HLOGC(bslog.Debug, @@ -386,7 +1577,7 @@ int32_t CSndBuffer::getMsgNoAtSeq(const int32_t seqno) // XXX Suboptimal procedure to keep the blocks identifiable // by sequence number. Consider using some circular buffer. int i; - CSndBlock* ee SRT_ATR_UNUSED = 0; + Block* ee SRT_ATR_UNUSED = 0; for (i = 0; i < offset && p; ++i) { ee = p; @@ -410,14 +1601,11 @@ int32_t CSndBuffer::getMsgNoAtSeq(const int32_t seqno) int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) { - // NOTE: w_packet.m_iSeqNo is expected to be set to the value - // of the sequence number with which this packet should be sent. - ScopedLock bufferguard(m_BufLock); int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); - CSndBlock* p = m_pFirstBlock; + Block* p = m_pFirstBlock; // XXX Suboptimal procedure to keep the blocks identifiable // by sequence number. Consider using some circular buffer. @@ -505,8 +1693,7 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti // As this function is predicted to extract the data to send as a rexmited packet, // the packet must be in the form ready to send - so, in case of encryption, // encrypted, and with all ENC flags already set. So, the first call to send - // the packet originally (the other overload of this function) must set these - // flags. + // the packet originally (readData) must set these flags first. w_packet.set_msgflags(p->m_iMsgNoBitset); w_srctime = p->m_tsOriginTime; @@ -524,7 +1711,7 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti sync::steady_clock::time_point CSndBuffer::getRexmitTime(const int32_t seqno) { ScopedLock bufferguard(m_BufLock); - const CSndBlock* p = m_pFirstBlock; + const Block* p = m_pFirstBlock; int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); if (offset < 0 || offset >= m_iCount) @@ -609,32 +1796,35 @@ int32_t CSndBuffer::popLostSeq(DropRange& w_drop) return seq; } -int CSndBuffer::getCurrBufSize() const +void CSndBuffer::removeLossUpTo(int32_t seqno) { - return m_iCount; + m_SndLossList.removeUpTo(seqno); } -int CSndBuffer::getMaxPacketLen() const +int CSndBuffer::insertLoss(int32_t lo, int32_t hi, const sync::steady_clock::time_point& pt SRT_ATR_UNUSED) { - return m_iBlockLen - m_iAuthTagSize; + return m_SndLossList.insert(lo, hi); } -int CSndBuffer::countNumPacketsRequired(int iPldLen) const +int CSndBuffer::getLossLength() { - const int iPktLen = getMaxPacketLen(); - return countNumPacketsRequired(iPldLen, iPktLen); + return m_SndLossList.getLossLength(); } -int CSndBuffer::countNumPacketsRequired(int iPldLen, int iPktLen) const +int CSndBuffer::getCurrBufSize() const { - return (iPldLen + iPktLen - 1) / iPktLen; + return m_iCount; } -namespace { -int round_val(double val) +int CSndBuffer::getMaxPacketLen() const { - return static_cast(round(val)); + return m_iBlockLen - m_iReservedSize; } + +int CSndBuffer::countNumPacketsRequired(int iPldLen) const +{ + const int iPktLen = getMaxPacketLen(); + return number_slices(iPldLen, iPktLen); } int CSndBuffer::getAvgBufSize(int& w_bytes, int& w_tsp) @@ -648,9 +1838,13 @@ int CSndBuffer::getAvgBufSize(int& w_bytes, int& w_tsp) // so rounding is beneficial, while for the number of // bytes in the buffer is a higher value, so rounding can be omitted, // but probably better to round all three values. - w_bytes = round_val(m_mavg.bytes()); - w_tsp = round_val(m_mavg.timespan_ms()); - return round_val(m_mavg.pkts()); + + // Using simple rounding, as it should be guaranteed that + // these values are never negative. If they are, the results + // would be stupid anyway. + w_bytes = m_mavg.bytes() + 0.49; + w_tsp = m_mavg.timespan_ms() + 0.49; + return int(m_mavg.pkts() + 0.49); } void CSndBuffer::updAvgBufSize(const steady_clock::time_point& now) @@ -672,7 +1866,10 @@ int CSndBuffer::getCurrBufSize(int& w_bytes, int& w_timespan) const * Also, if there is only one pkt in buffer, the time difference will be 0. * Therefore, always add 1 ms if not empty. */ - w_timespan = 0 < m_iCount ? (int) count_milliseconds(m_tsLastOriginTime - m_pFirstBlock->m_tsOriginTime) + 1 : 0; + if (m_iCount > 0) + w_timespan = count_milliseconds(m_tsLastOriginTime - m_pFirstBlock->m_tsOriginTime) + 1; + else + w_timespan = 0; return m_iCount; } @@ -750,30 +1947,52 @@ int CSndBuffer::dropAll(int& w_bytes) return dpkts; } +CSndBuffer::~CSndBuffer() +{ + Block* pb = m_pBlock->m_pNext; + while (pb != m_pBlock) + { + Block* temp = pb; + pb = pb->m_pNext; + delete temp; + } + delete m_pBlock; + + while (m_pFirstMemSlice != NULL) + { + MemSlice* temp = m_pFirstMemSlice; + m_pFirstMemSlice = m_pFirstMemSlice->m_pNext; + delete[] temp->m_pcData; + delete temp; + } + + releaseMutex(m_BufLock); +} + // This does the same as increase(); try to find common parts or make // these two common. void CSndBuffer::initialize() { // initial physical buffer of "size" - m_pBuffer = new Buffer; - m_pBuffer->m_pcData = new char[m_iSize * m_iBlockLen]; - m_pBuffer->m_iSize = m_iSize; - m_pBuffer->m_pNext = NULL; + m_pFirstMemSlice = new MemSlice; + m_pFirstMemSlice->m_pcData = new char[m_iSize * m_iBlockLen]; + m_pFirstMemSlice->m_iSize = m_iSize; + m_pFirstMemSlice->m_pNext = NULL; // circular linked list for out bound packets - m_pBlock = new CSndBlock; - CSndBlock* pb = m_pBlock; - char* pc = m_pBuffer->m_pcData; + m_pBlock = new Block; + Block* pb = m_pBlock; + char* pslice = m_pFirstMemSlice->m_pcData; for (int i = 0; i < m_iSize; ++i) { pb->m_iMsgNoBitset = 0; - pb->m_pcData = pc; - pc += m_iBlockLen; + pb->m_pcData = pslice; + pslice += m_iBlockLen; if (i < m_iSize - 1) { - pb->m_pNext = new CSndBlock; + pb->m_pNext = new Block; pb = pb->m_pNext; } } @@ -785,13 +2004,13 @@ void CSndBuffer::initialize() void CSndBuffer::increase() { - int unitsize = m_pBuffer->m_iSize; + int unitsize = m_pFirstMemSlice->m_iSize; // new physical buffer - Buffer* nbuf = NULL; + MemSlice* nbuf = NULL; try { - nbuf = new Buffer; + nbuf = new MemSlice; nbuf->m_pcData = new char[unitsize * m_iBlockLen]; } catch (...) @@ -803,26 +2022,26 @@ void CSndBuffer::increase() nbuf->m_pNext = NULL; // insert the buffer at the end of the buffer list - Buffer* p = m_pBuffer; + MemSlice* p = m_pFirstMemSlice; while (p->m_pNext != NULL) p = p->m_pNext; p->m_pNext = nbuf; // new packet blocks - CSndBlock* nblk = NULL; + Block* nblk = NULL; try { - nblk = new CSndBlock; + nblk = new Block; } catch (...) { delete nblk; throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } - CSndBlock* pb = nblk; + Block* pb = nblk; for (int i = 1; i < unitsize; ++i) { - pb->m_pNext = new CSndBlock; + pb->m_pNext = new Block; pb = pb->m_pNext; } @@ -831,12 +2050,12 @@ void CSndBuffer::increase() m_pLastBlock->m_pNext = nblk; pb = nblk; - char* pc = nbuf->m_pcData; + char* pslice = nbuf->m_pcData; for (int i = 0; i < unitsize; ++i) { - pb->m_pcData = pc; + pb->m_pcData = pslice; pb = pb->m_pNext; - pc += m_iBlockLen; + pslice += m_iBlockLen; } m_iSize += unitsize; @@ -847,4 +2066,40 @@ void CSndBuffer::increase() << " (total size: " << m_iSize << " bytes)"); } +std::string CSndBuffer::show() const +{ + using namespace hvu; + + ofmtbufstream out; + + int offset = 0; + int seqno = m_iSndLastDataAck; + + fmtc findex = fmtc().width(3).fillzero(); + + Block* p = m_pFirstBlock; + + while (p != m_pLastBlock) + { + out << "[" << fmt(offset, findex) << "]%" << seqno << ": " + << p->m_iLength << "!" << BufferStamp(p->m_pcData, p->m_iLength); + + out.puts(); + ++offset; + seqno = CSeqNo::incseq(seqno); + p = p->m_pNext; + } + + return out.str(); + +} + +// Stubs, unused in old buffer + +bool CSndBuffer::reserveSeqno(int32_t ) { return true; } +bool CSndBuffer::releaseSeqno(int32_t ) { return true; } +bool CSndBuffer::cancelLostSeq(int32_t) { return false; } + +#endif + } // namespace srt diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index 97b0abc1a..f7e76a67f 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -53,11 +53,15 @@ modified by #ifndef INC_SRT_BUFFER_SND_H #define INC_SRT_BUFFER_SND_H +#include + #include "srt.h" #include "packet.h" #include "buffer_tools.h" #include "list.h" +#define SRT_SNDBUF_NEW 1 + // The notation used for "circular numbers" in comments: // The "cicrular numbers" are numbers that when increased up to the // maximum become zero, and similarly, when the zero value is decreased, @@ -84,8 +88,6 @@ struct CSndBlock time_point m_tsRexmitTime; // packet retransmission time int m_iTTL; // time to live (milliseconds) - CSndBlock* m_pNext; // next block - int32_t getMsgSeq() { // NOTE: this extracts message ID with regard to REXMIT flag. @@ -96,6 +98,233 @@ struct CSndBlock } }; +#if SRT_SNDBUF_NEW + +struct PacketContainer +{ + typedef sync::steady_clock::time_point time_point; + + struct Packet: CSndBlock + { + // Defines the time of the next retransmission. + // If zero-time, this packet is not to be retransmitted. + // If future-time, this packet should be skipped when looking for the + // packets for retransmission, but the time remains unchanged. This + // will be set to zero-time right after picking up for retransmission. + time_point m_tsNextRexmitTime; + + // Rexmit system: linked list inside a container. + // + // m_iFirstRexmit and m_iLastRexmit keep the index of the first and + // last retransmission request record. If there are no such records, + // both should be -1. The last is to speed up place search for inserting + // newly incoming loss reports. + // + // The packet cell pointed by m_iFirstRexmit contains information: + // - m_iLossLength: how many consecutive packets belong to this group + // - m_iNextLossGroupOffset: distance between this cell and the next group, + // or 0 if this is the last consecutive group. + // + // These data are meaningful only for packets that are first in the consecutive + // group of retransmission schedule. In all other packets both should be 0. + + int m_iLossLength; + int m_iNextLossGroupOffset; + }; + +private: + + /// Spare storage with memory blocks used for a single packet. + BufferedMessageStorage m_Storage; + + /// Container for the packets; managed internally with data consistency. + std::deque m_Container; + + /// Kept in sync with m_Container.size() to allow calling size() without locking. + sync::atomic m_iCachedSize; + + /// Distance between the newly stored packet and the end of container. + /// This counts the number of packets since the push-end that were not + /// yet sent as unique. Shifted by 1 (that is, decreased) with every packet + /// extracted by readData(). If 0, there are no new unique packets. + int m_iNewQueued; + + /// This field keeps the index of the first packet that has an active + /// retransmission request. -1 if there are no retransmission requests. + int m_iFirstRexmit; + + /// This keeps the index of the packet in the retransmission request + /// list that was last inserted. This shortcuts searching for the + /// existing retransmission records in a normal situation, when the + /// incoming insertion request follows the last of the records. If + /// this isn't the case, searching starts from the first record. + /// -1 if there is no retransmission request. + int m_iLastRexmit; + + /// Cached loss length. This is rarely required, but algorithms + /// for NAK report use it to determine the period. + sync::atomic m_iLossLengthCache; + + // Retransmission request list structure: + // + // Packets that are requested retransmission are set m_tsNextRexmitTime + // to the value of the time that must be in the past to be retransmitted. + // + // Any insertion also updates the following: + // - m_iFirstRexmit is set to the index of the first packet, or unchanged + // if the inserted sequence pair was not the very first + // - m_iLastRexmit is set to the first packet of the group, if this was + // the very last insertion (if the current inserion was past the previous + // last one) + // - CPacket::m_iLossLength is the number of consecutive packets since + // this packet that belong to the retransmission-requested. Note that + // also only this packet contains a nonzero m_tsNextRexmitTime field. + // - CPacket::m_iNextLossGroupOffset is set to 0 if this was inserted as + // the last one, or to the offset between this packet and the nearest + // packet beginning the next retransmission group + // + // Revocation of the packets updates the fields: + // - If the series of packets are split in half, the first packet that + // survives the revocation is updated: the m_iLossLength is set to + // the new size of the group, m_iFirstRexmit is set to 0. + // - If the whole series are revoked, only the m_iFirstRexmit field + // is updated to the new beginning. + // - If all packets with retransmission requests are effectively removed, + // both m_iFirstRexmit and m_iLastRexmit fields are set to -1. + // - No matter if any groups were removed or not, m_iFirstRexmit and + // m_iLastRexmit fields are being updated by decreasing by the number + // of revoked packets, if they are not set the new value. + // + // Expiration of a packet (per TTL, for example) causes m_tsNextRexmitTime + // to be reset to zero, but no other action is undertaken. The packet is + // still in the retransmission request record, just won't be retransmitted. + // + // Popping a loss does the following: + // - The first packet group, pointed by m_iFirstRexmit, is checked and removed + // - Removal means that the next packet is taken as the first one: + // - if m_iLossLength == 1, take the packet distant by m_iNextLossGroupOffset + // - if m_iLossLength > 1, take the packet distant by 1, set it the + // values of this packet's m_iLossLength and m_iNextLossGroupOffset + // decreased by 1 + // - the m_iFirstRexmit index is updated to point to this new packet + // - If the packet at this position has m_tsNextRexmitTime zero, this + // is not reported as retransmission-eligible, but still removed, + // that is, after removal the whole procedure starts over + // - If the search with removed expired retransmission packets reaches + // a packet with m_iNextLossGroupOffset == 0, finally "no rexmit request" + // state is reported, same as when m_iFirstRexmit == -1. + // - If a removal resulted in removing the last record, whether a valid + // retransmission request or not, m_iFirstRexmit and m_iLastRexmit + // are set to -1. + +public: + + PacketContainer(size_t payload_len, size_t max_packets): + m_Storage(payload_len, max_packets), + m_iCachedSize(0), + m_iNewQueued(0), + m_iFirstRexmit(-1), + m_iLastRexmit(-1), + m_iLossLengthCache(0) + { + } + + int unique_size() const { return m_iNewQueued; } + + // GET, means the packet is still in the container, you just get access to it. + // This call however changes the status of the retrieved packet by removing it + // from the unique range and moving it to the history range. + Packet* get_unique(); + + void set_expired(int upindex) + { + int remain_unique = m_Container.size() - upindex; + + // if remain_unique > m_iNewQueued, it means that packets up to upindex + // are already expired. + m_iNewQueued = std::min(m_iNewQueued, remain_unique); + } + + Packet& push() + { + m_Container.push_back(Packet()); + m_iCachedSize = m_Container.size(); + + Packet& that = m_Container.back(); + + // Allocate the packet payload space + that.m_iLength = m_Storage.blocksize; + that.m_pcData = m_Storage.get(); + + // pushing is always treated as adding a new unique packet + ++m_iNewQueued; + + // Return as is - without initialized fields. + return that; + } + + size_t pop(size_t n = 1); + + void remove_loss(int n); + + bool clear_loss(int index); + + //void remove_loss_seq(int32_t seqhi); + bool insert_loss(int ixlo, int ixhi, const time_point&); + + void set_rexmit_time(int ixlo, int ixhi, const time_point& time) + { + for (int i = ixlo; i <= ixhi; ++i) + { + // Do not override existing time value, only set anew if 0 + if (is_zero(m_Container[i].m_tsNextRexmitTime)) + m_Container[i].m_tsNextRexmitTime = time; + } + } + + int next_loss(int current_loss) + { + if (current_loss == -1) + return -1; + + Packet& p = m_Container[current_loss]; + if (p.m_iNextLossGroupOffset == 0) + return -1; // The last loss + + return current_loss + p.m_iNextLossGroupOffset; + } + + int loss_length() const { return m_iLossLengthCache; } + + int extractFirstLoss(); + + size_t size() const + { + return m_iCachedSize; + } + bool empty() const { return m_iCachedSize == 0; } + + void clear(); + + // NOTE: operator[] is unchecked. Use indirectly. + Packet& operator[](size_t index) { return m_Container[index]; } + const Packet& operator[](size_t index) const { return m_Container[index]; } + + struct PacketShowState + { + time_point begin_time; + int remain_loss_group; // SIZE. 1+ if any loss noted, 0 if no loss. + int next_loss_begin; // INDEX. -1 if no loss pointed + + PacketShowState(): remain_loss_group(0), next_loss_begin(-1) {} + }; + + void showline(int index, PacketShowState& st, hvu::ofmtbufstream& out) const; +}; + +#else +#endif + class CSndBuffer { typedef sync::steady_clock::time_point time_point; @@ -111,13 +340,13 @@ class CSndBuffer // We have the following split for a single packet: // // [ ----------------------------- MSS ---------------------------------------------] - // [HEADER(IP-dependent)][ ................... PAYLOAD .................. ][auth tag] + // [HEADER(IP-dependent)][ ................... PAYLOAD .................. ][reserved] CSndBuffer(size_t bytesize, // size limit in bytes (will be split into packets) size_t slicesize, // size of the single memory chunk for payload buffers size_t mss, // value of the MSS (default: 1500, take from settings) size_t headersize, // size of the packet header (IP version dependent) - size_t reservedsize, // Size reserved in the payload, but not for use for data. + size_t reservedsize, // Size reserved in the payload, but not for the transferred data int flow_window_size // required for loss list init ); ~CSndBuffer(); @@ -218,7 +447,16 @@ class CSndBuffer int dropAll(int& bytes); int getAvgBufSize(int& bytes, int& timespan); + +#if SRT_SNDBUF_NEW + int getCurrBufSize(int& bytes, int& timespan) const + { + sync::ScopedLock lk (m_BufLock); + return getBufferStats((bytes), (timespan)); + } +#else int getCurrBufSize(int& bytes, int& timespan) const; +#endif /// Retrieve input bitrate in bytes per second int getInputRate() const { return m_rateEstimator.getInputRate(); } @@ -240,11 +478,6 @@ class CSndBuffer SRT_TSA_NEEDS_NONLOCKED(m_BufLock) duration getBufferingDelay(const time_point& tnow) const; - /// @brief Count the number of required packets to store the payload (message). - /// @param iPldLen the length of the payload to check. - /// @return the number of required data packets. - int countNumPacketsRequired(int iPldLen) const; - /// Get maximum payload length per packet. int getMaxPacketLen() const; @@ -254,80 +487,100 @@ class CSndBuffer void overrideFirstSeqNo(int32_t seq) { m_iSndLastDataAck = seq; } // Sender loss list management methods - void removeLossUpTo(int32_t seqno) - { - m_SndLossList.removeUpTo(seqno); - } - - int insertLoss(int32_t lo, int32_t hi) - { - return m_SndLossList.insert(lo, hi); - } - + void removeLossUpTo(int32_t seqno); + int insertLoss(int32_t lo, int32_t hi, const sync::steady_clock::time_point& pt = sync::steady_clock::time_point()); int32_t popLostSeq(DropRange&); - int getLossLength() - { - return m_SndLossList.getLossLength(); - } - -private: + int getLossLength(); - void updAvgBufSize(const time_point& time); + bool cancelLostSeq(int32_t seq); /// @brief Count the number of required packets to store the payload (message). /// @param iPldLen the length of the payload to check. - /// @param iMaxPktLen the maximum payload length of the packet (the value returned from getMaxPacketLen()). /// @return the number of required data packets. - int countNumPacketsRequired(int iPldLen, int iMaxPktLen) const; + int countNumPacketsRequired(int iPldLen) const; + + std::string show() const; - //uint64_t getInRatePeriod() const { return m_rateEstimator.getInRatePeriod(); } + /// Reserves the sequence number that must prevail, even if + /// it's covered by ACK. + bool reserveSeqno(int32_t seq); + bool releaseSeqno(int32_t new_ack_seq); private: + void initialize(); - void increase(); -private: + void updAvgBufSize(const time_point& time); + + // SENDER BUFFER FUNCTIONAL FIELDS + mutable sync::Mutex m_BufLock; // used to synchronize buffer operation + // Note: as constants, these fields do not need mutex protection + // also when they are used in calcualtions. + const int m_iBlockLen; // maximum length of a block holding packet payload and AUTH tag (excluding packet header). + const int m_iReservedSize; // Authentication tag size (if GCM is enabled). + sync::atomic m_iSndLastDataAck; // The real last ACK that updates the sender buffer and loss list + int32_t m_iNextMsgNo; // next message number + int m_iBytesCount; // number of payload bytes in queue + time_point m_tsLastOriginTime; + + AvgBufSize m_mavg; + CRateEstimator m_rateEstimator; + +#if SRT_SNDBUF_NEW + + /// Buffer capacity (maximum size), used intermediately and in initialization only. + int m_iCapacity; + + /// Reserved lowest sequence number that should guarantee being + /// still in the buffer, or SRT_SEQNO_NONE if no guarantee was requested. + /// The call to revoke will not remove these packets, even if succeeds this. + sync::atomic m_iSndReservedSeq; + + PacketContainer m_Packets; + + int getBufferStats(int& bytes, int& timespan) const; + +#else + void increase(); + CSndLossList m_SndLossList; // Sender loss list - CSndBlock* m_pBlock; - CSndBlock* m_pFirstBlock; - CSndBlock* m_pCurrBlock; - CSndBlock* m_pLastBlock; + struct Block: CSndBlock + { + Block* m_pNext; // next block + }; + + Block* m_pBlock; + Block* m_pFirstBlock; + Block* m_pCurrBlock; + Block* m_pLastBlock; // m_pBlock: The head pointer // m_pFirstBlock: The first block // m_pCurrBlock: The current block // m_pLastBlock: The last block (if first == last, buffer is empty) - struct Buffer + struct MemSlice { char* m_pcData; // buffer int m_iSize; // size - Buffer* m_pNext; // next buffer - } * m_pBuffer; // physical buffer - - int32_t m_iNextMsgNo; // next message number + MemSlice* m_pNext; // next buffer + } * m_pFirstMemSlice; // physical buffer int m_iSize; // buffer size (number of packets) - const int m_iBlockLen; // maximum length of a block holding packet payload and AUTH tag (excluding packet header). - const int m_iAuthTagSize; // Authentication tag size (if GCM is enabled). - // NOTE: This is atomic AND under lock because the function getCurrBufSize() // is returning it WITHOUT locking. Modification, however, must stay under // a lock. sync::atomic m_iCount; // number of used blocks - int m_iBytesCount; // number of payload bytes in queue - time_point m_tsLastOriginTime; +#endif - AvgBufSize m_mavg; - CRateEstimator m_rateEstimator; -private: + // deleted copyers CSndBuffer(const CSndBuffer&); CSndBuffer& operator=(const CSndBuffer&); }; diff --git a/srtcore/common.h b/srtcore/common.h index 905537673..c9cc0a9d8 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -1557,6 +1557,62 @@ struct LocalInterface std::vector GetLocalInterfaces(); + +struct BufferedMessageStorage +{ + size_t blocksize; + size_t maxstorage; + std::vector storage; + + BufferedMessageStorage(size_t blk, size_t max = 0): + blocksize(blk), maxstorage(max), storage() + { + } + + char* get() + { + if (storage.empty()) + return new char[blocksize]; + + // Get the element from the end + char* block = storage.back(); + storage.pop_back(); + return block; + } + + // Reserve nblocks of messages. Still, do not exceed + void reserve(size_t nblocks = 0) + { + if (nblocks == 0 || nblocks + storage.size() > maxstorage) + nblocks = maxstorage; + + for (size_t i = 0; i < nblocks; ++i) + { + char* block = new char[blocksize]; + storage.push_back(block); + } + } + + void put(char* block) + { + if (storage.size() >= maxstorage) + { + // Simply delete + delete[] block; + return; + } + + // Put the block into the spare buffer + storage.push_back(block); + } + + ~BufferedMessageStorage() + { + for (size_t i = 0; i < storage.size(); ++i) + delete[] storage[i]; + } +}; + } // namespace srt #endif diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 6221a99de..5f34ec7dc 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -6815,6 +6815,7 @@ int CUDT::sndDropTooLate() m_stats.sndr.dropped.count(stats::BytesPackets((uint64_t) dbytes, (uint32_t) dpkts)); leaveCS(m_StatsLock); + // NOTE: This sequence number involves also reserved data, if any. m_iSndLastAck = m_pSndBuffer->firstSeqNo(); /* If we dropped packets not yet sent, advance current position */ @@ -8616,6 +8617,10 @@ void CUDT::revokeACKedSequences(int32_t ackdata_seqno) if (!m_pSndBuffer->revoke(ackdata_seqno)) return; + // Rephrase this - revoke() need not remove all up to ackdata_seqno + // if there are reserved cells by sender buffer. + // XXX SRT_ASSERT(m_pSndBuffer->firstSeqNo() == ackdata_seqno); + #if SRT_ENABLE_BONDING if (is_group) { @@ -9788,7 +9793,7 @@ int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, CPacket& w_packet, srt::s if (payload == CSndBuffer::READ_NONE) { - LOGC(qslog.Error, log << CONID() << "loss-reported packet %" << w_packet.seqno() << " NOT FOUND in the sender buffer"); + LOGC(qslog.Error, log << CONID() << "loss-reported packet %" << seqno << " NOT FOUND in the sender buffer"); return 0; } @@ -9850,6 +9855,9 @@ int CUDT::packLostData(CPacket& w_packet) if (seqno == SRT_SEQNO_NONE) return 0; + HLOGC(qslog.Debug, log << "REXMIT: got %" << seqno << ", requesting that packet from sndbuf with first %" + << m_pSndBuffer->firstSeqNo()); + // Extract the packet from the sender buffer that is mapped to the expected sequence // number, bypassing and taking care of those that are decided to be dropped. const int payload = extractCleanRexmitPacket(seqno, (w_packet), (tsOrigin)); @@ -10000,6 +10008,7 @@ snd_logger g_snd_logger; void CUDT::setPacketTS(CPacket& p, const time_point& ts) { + SRT_ASSERT(!is_zero(ts)); enterCS(m_StatsLock); const time_point tsStart = m_stats.tsStartTime; leaveCS(m_StatsLock); @@ -10145,6 +10154,15 @@ void CUDT::updateSenderMeasurements(bool can_rexmit SRT_ATR_UNUSED) } +bool CUDT::releaseSend() +{ + ScopedLock lkrack (m_RecvAckLock); + if (!m_pSndBuffer) + return false; + + return m_pSndBuffer->releaseSeqno(m_iSndLastAck); +} + bool CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, CNetworkInterface& w_src_addr) { int payload = 0; diff --git a/srtcore/core.h b/srtcore/core.h index bb927ec3e..6136b227f 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1334,7 +1334,9 @@ class CUDT /// @return true if a packet has been packets; false otherwise. bool packUniqueData(CPacket& packet); - /// Pack in CPacket the next data to be send. + /// Pack in CPacket the next data to be send. If the call succeeds, the sequence number + /// of the packet is reserved and guaranteed to stay in the buffer. This should be later + /// released by calling releaseSend(packet). /// /// @param packet [out] a CPacket structure to fill /// @param nexttime [out] Time when this socket should be next time picked up for processing. @@ -1343,6 +1345,7 @@ class CUDT /// @retval true A packet was extracted for sending, the socket should be rechecked at @a nexttime /// @retval false Nothing was extracted for sending, @a nexttime should be ignored bool packData(CPacket& packet, time_point& nexttime, CNetworkInterface& src_addr); + bool releaseSend(); /// Also excludes srt::CUDTUnited::m_GlobControlLock. SRT_TSA_NEEDS_NONLOCKED(m_RcvTsbPdStartupLock, m_StatsLock, m_RecvLock, m_RcvLossLock, m_RcvBufferLock) diff --git a/srtcore/group.cpp b/srtcore/group.cpp index a44bc8326..66e64cf05 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -4231,7 +4231,7 @@ void CUDTGroup::internalKeepalive(SocketData* gli) } // Use the bigger size of SRT_MAX_PLSIZE to potentially fit both IPv4/6 -CUDTGroup::BufferedMessageStorage CUDTGroup::BufferedMessage::storage(SRT_MAX_PLSIZE_AF_INET /*, 1000*/); +BufferedMessageStorage CUDTGroup::BufferedMessage::storage(SRT_MAX_PLSIZE_AF_INET /*, 1000*/); // Forwarder needed due to class definition order int32_t CUDTGroup::generateISN() diff --git a/srtcore/group.h b/srtcore/group.h index d6528df7b..15b67be00 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -542,50 +542,6 @@ class CUDTGroup return m_iBusy || !m_Group.empty(); } - struct BufferedMessageStorage - { - size_t blocksize; - size_t maxstorage; - std::vector storage; - - BufferedMessageStorage(size_t blk, size_t max = 0) - : blocksize(blk) - , maxstorage(max) - , storage() - { - } - - char* get() - { - if (storage.empty()) - return new char[blocksize]; - - // Get the element from the end - char* block = storage.back(); - storage.pop_back(); - return block; - } - - void put(char* block) - { - if (storage.size() >= maxstorage) - { - // Simply delete - delete[] block; - return; - } - - // Put the block into the spare buffer - storage.push_back(block); - } - - ~BufferedMessageStorage() - { - for (size_t i = 0; i < storage.size(); ++i) - delete[] storage[i]; - } - }; - struct BufferedMessage { static BufferedMessageStorage storage; diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 6166bdd9b..813f71343 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -239,12 +239,12 @@ bool CSendOrderList::update(SocketHolder::sockiter_t point, SocketHolder::EResch } #endif + ScopedLock listguard(m_ListLock); if (!n.pinned()) { // New insert, not considering reschedule. HLOGC(qslog.Debug, log << "CSndUList: UPDATE: inserting @" << point->id() << " anew T=" << FormatTime(ts) << nowrel.str()); - ScopedLock listguard(m_ListLock); m_Schedule.insert(ts, point); if (n.is_top()) { @@ -262,8 +262,6 @@ bool CSendOrderList::update(SocketHolder::sockiter_t point, SocketHolder::EResch return false; } - ScopedLock listguard(m_ListLock); - // NOTE: Rescheduling means to speed up release time. So apply only if new time is earlier. if (n.time <= ts) { @@ -552,6 +550,7 @@ void CSndQueue::workerSendOrder() HLOGC(qslog.Debug, log << CONID() << "chn:SENDING: " << pkt.Info()); m_pChannel->sendto(addr, pkt, source_addr); + u.releaseSend(); } THREAD_EXIT(); diff --git a/srtcore/utilities.h b/srtcore/utilities.h index 88e701786..23cda11b4 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -1363,6 +1363,12 @@ inline T CountIIR(T base, T newval, double factor) return base+T(diff*factor); } +template +inline Integer number_slices(Integer total_size, Integer slice_size) +{ + return (total_size + slice_size - 1) / slice_size; +} + // Property accessor definitions // diff --git a/test/filelist.maf b/test/filelist.maf index 7a41bf396..9972f4f9c 100644 --- a/test/filelist.maf +++ b/test/filelist.maf @@ -5,6 +5,7 @@ test_env.h SOURCES test_main.cpp test_buffer_rcv.cpp +test_buffer_snd.cpp test_common.cpp test_connection_timeout.cpp test_crypto.cpp diff --git a/test/test_buffer_snd.cpp b/test/test_buffer_snd.cpp new file mode 100644 index 000000000..110354a01 --- /dev/null +++ b/test/test_buffer_snd.cpp @@ -0,0 +1,238 @@ + +#include "gtest/gtest.h" +#include "buffer_snd.h" +#include +#include +#include "ofmt.h" + + +using namespace std; +using namespace srt; +using namespace srt::sync; + +class TestSndBuffer: public testing::Test +{ + using time_point = steady_clock::time_point; + +public: + + hvu::ofmtrefstream sout {cout}; + + unique_ptr m_buffer; + + int32_t last_seqno; + + TestSndBuffer() + { + // initialization code here + } + + virtual ~TestSndBuffer() + { + // cleanup any pending stuff, but no exceptions allowed + } + + void addBuffer(const char* data, int len, int msgno, int ttl = -1) + { + SRT_MSGCTRL c = srt_msgctrl_default; + c.pktseq = last_seqno; + c.msgno = msgno; + c.msgttl = ttl; + + m_buffer->addBuffer(data, len, (c)); + last_seqno = c.pktseq; + } + + void revokeSeq(int32_t seqno) + { + m_buffer->revoke(seqno); + } + + void scheduleRexmit(int32_t seqlo, int32_t seqhi, steady_clock::duration uptime = steady_clock::duration()) + { + //time_point sched_at = steady_clock::now() + uptime; + m_buffer->insertLoss(seqlo, seqhi, steady_clock::now() + uptime); + } + + void cancelRexmit(int32_t seq) + { + m_buffer->cancelLostSeq(seq); + } + + size_t readUnique(CPacket& pkt, int kflg) + { + int pktskipseqno = 0; + time_point tsOrigin; + + int size = m_buffer->readData((pkt), (tsOrigin), kflg, (pktskipseqno)); + // Ignore intermediate data + + return size_t(size); + } + + size_t readOld(int32_t seqno, CPacket& w_pkt, std::list>& w_dropseq) + { + for (;;) + { + time_point tsOrigin; + + CSndBuffer::DropRange drop; + int size = m_buffer->readOldPacket(seqno, (w_pkt), (tsOrigin), (drop)); + + if (size == CSndBuffer::READ_DROP) + { + w_dropseq.emplace_back(drop.seqno[0], drop.seqno[1]); + continue; + } + + // 0 and >0 are handled common way + return size; + } + } + + int32_t popLoss() + { + CSndBuffer::DropRange drop; + return m_buffer->popLostSeq((drop)); + } + + int lossLength() + { + return m_buffer->getLossLength(); + } + +protected: + // SetUp() is run immediately before a test starts. + void SetUp() override + { + // make_unique is unfortunatelly C++14 + m_buffer.reset(new CSndBuffer(32*1024, 1024, 1500, CPacket::udpHeaderSize(AF_INET), 0, 8192)); + last_seqno = 12345; + } + + void TearDown() override + { + // Code here will be called just after the test completes. + // OK to throw exceptions from here if needed. + m_buffer.reset(); + } + +}; + +TEST_F(TestSndBuffer, Basic) +{ + for (int i = 1; i < 11; ++i) + { + addBuffer("BUFFERDATA", 10, i); + } + + sout.puts("BUFFER STATUS:"); + sout.puts(m_buffer->show()); + + // Now let's read 3 packets from it + + CPacket rpkt; + EXPECT_NE(readUnique((rpkt), 0), 0); + EXPECT_NE(readUnique((rpkt), 0), 0); + EXPECT_NE(readUnique((rpkt), 0), 0); + + // AFTER READING you need to declare you no longer need them. + m_buffer->releaseSeqno(12345); // we haven't ACKed anything yet. + + // And let's see + + sout.puts("AFTER extracting 3 packets:"); + sout.puts(m_buffer->show()); + + // Now let's schedule 12346 and 12347 for rexmit + scheduleRexmit(12346, 12347); + + sout.puts("AFTER scheduling #1 and #2 for rexmit:"); + sout.puts(m_buffer->show()); + + // Now remove up to the second one + revokeSeq(12347); // this is the first seq that should stay + + sout.puts("AFTER ACK #0 and #1:"); + sout.puts(m_buffer->show()); + + // Now read 4 more packets + EXPECT_NE(readUnique((rpkt), 0), 0); + EXPECT_NE(readUnique((rpkt), 0), 0); + EXPECT_NE(readUnique((rpkt), 0), 0); + EXPECT_NE(readUnique((rpkt), 0), 0); + + m_buffer->releaseSeqno(12345); // we haven't ACKed anything yet. + + // Then add two rexmit requests + scheduleRexmit(12348, 12349); + scheduleRexmit(12351, 12352); + + sout.puts("AFTER read 4, and loss-report: 12348-12349 and 12351-12352"); + sout.puts(m_buffer->show()); + + // Ok, you should have now losses in order: + // 12347 - 12349, 12351 - 12352 + + EXPECT_EQ(popLoss(), 12347); + EXPECT_EQ(popLoss(), 12348); + EXPECT_EQ(popLoss(), 12349); + EXPECT_EQ(popLoss(), 12351); + + EXPECT_EQ(lossLength(), 1); + + sout.puts("AFTER 4 times loss was popped:"); + sout.puts(m_buffer->show()); + + sout.puts("Scheduled rexmit: 12348-12350 (3)"); + scheduleRexmit(12348, 12350); + EXPECT_EQ(lossLength(), 4); + sout.puts(m_buffer->show()); + + sout.puts("Scheduled rexmit: and 12351-12353 (3)"); + scheduleRexmit(12351, 12353); + + EXPECT_EQ(lossLength(), 6); + + // NEXT TESTS: + // + // 1. Add losses that cover existing losses pre- and post, with multiple records + // 2. Add gluing-in losses + // 3. Clear a single loss with 0-time and test how itś skipped. + // 4. Set future loss time, followed by 0-time and see skipping with pop(). + + sout.puts(m_buffer->show()); + + // Ok so let's cancel now 12350 and lift the time of 12351 in the future + cancelRexmit(12350); + cancelRexmit(12351); + scheduleRexmit(12351, 12351, milliseconds_from(500)); // 0.5s in the future + + sout.puts("Cleared 12350 and set 12351 0.5s in the future"); + sout.puts(m_buffer->show()); + + // Now extract a loss 3 times. 50 should be wiped and 51 skipped. + EXPECT_EQ(popLoss(), 12348); + EXPECT_EQ(popLoss(), 12349); + + EXPECT_EQ(popLoss(), 12352); + + sout.puts("After extracting 12348, 12349 and 12352"); + sout.puts(m_buffer->show()); + + sout.puts("Sleep for 0.5s to make 12351 future-expire"); + std::this_thread::sleep_for(500ms); + + sout.puts("Now 12351 should be extracted, then 12353"); + EXPECT_EQ(popLoss(), 12351); + EXPECT_EQ(popLoss(), 12353); + + // And all loss reports should be gone + EXPECT_EQ(lossLength(), 0); + + sout.puts(m_buffer->show()); +} + +// Second test for sender buffer should use multiple threads for +// scheduling packets, scheduling losses, and picking up packets for sending. + diff --git a/test/test_socket_options.cpp b/test/test_socket_options.cpp index 8838ec9e5..5ce0fcc7f 100644 --- a/test/test_socket_options.cpp +++ b/test/test_socket_options.cpp @@ -10,6 +10,7 @@ * Haivision Systems Inc. */ +#include #include #include #include @@ -18,7 +19,6 @@ #include "test_env.h" // SRT includes -#include "any.hpp" #include "socketconfig.h" #include "srt.h" @@ -158,9 +158,9 @@ class TestGroupOptions: public TestOptionsCommon #endif #if PLEASE_LOG -static std::string to_string(const linb::any& val) +static std::string to_string(const std::any& val) { - using namespace linb; + using namespace std; if (val.type() == typeid(const char*)) { @@ -274,11 +274,11 @@ struct OptionTestEntry const char* optname; // TODO: move to a separate array, function or std::map. RestrictionType restriction; // TODO: consider using SRTO_R_PREBIND, etc. from core.cpp size_t opt_len; - linb::any min_val; - linb::any max_val; - linb::any dflt_val; - linb::any ndflt_val; - vector invalid_vals; + std::any min_val; + std::any max_val; + std::any dflt_val; + std::any ndflt_val; + vector invalid_vals; Flags::type flags; bool allof() const { return true; } @@ -437,10 +437,10 @@ bool CheckDefaultValue(const OptionTestEntry& entry, SRTSOCKET sock, const char* { LOGD(cerr << "Will check default value: " << entry.optname << " = " << to_string(entry.dflt_val) << ": " << desc << endl); try { - const ValueType dflt_val = linb::any_cast(entry.dflt_val); + const ValueType dflt_val = std::any_cast(entry.dflt_val); CheckGetSockOpt(entry, sock, dflt_val, desc); } - catch (const linb::bad_any_cast&) + catch (const std::bad_any_cast&) { std::cerr << entry.optname << " default value type: " << entry.dflt_val.type().name() << "\n"; return false; @@ -453,16 +453,16 @@ template bool CheckSetNonDefaultValue(const OptionTestEntry& entry, SRTSOCKET sock, int expected_return, const char* desc) { try { - /*const ValueType dflt_val = linb::any_cast(entry.dflt_val); - const ValueType min_val = linb::any_cast(entry.min_val); - const ValueType max_val = linb::any_cast(entry.max_val);*/ + /*const ValueType dflt_val = std::any_cast(entry.dflt_val); + const ValueType min_val = std::any_cast(entry.min_val); + const ValueType max_val = std::any_cast(entry.max_val);*/ //const ValueType ndflt_val = (min_val != dflt_val) ? min_val : max_val; - const ValueType ndflt_val = linb::any_cast(entry.ndflt_val);; + const ValueType ndflt_val = std::any_cast(entry.ndflt_val);; CheckSetSockOpt(entry, sock, ndflt_val, expected_return, desc); } - catch (const linb::bad_any_cast&) + catch (const std::bad_any_cast&) { std::cerr << entry.optname << " non-default value type: " << entry.ndflt_val.type().name() << "\n"; return false; @@ -475,13 +475,13 @@ template bool CheckMinValue(const OptionTestEntry& entry, SRTSOCKET sock, const char* desc) { try { - const ValueType min_val = linb::any_cast(entry.min_val); + const ValueType min_val = std::any_cast(entry.min_val); CheckSetSockOpt(entry, sock, min_val, SRT_SUCCESS, desc); - const ValueType dflt_val = linb::any_cast(entry.dflt_val); + const ValueType dflt_val = std::any_cast(entry.dflt_val); CheckSetSockOpt(entry, sock, dflt_val, SRT_SUCCESS, desc); } - catch (const linb::bad_any_cast&) + catch (const std::bad_any_cast&) { std::cerr << entry.optname << " min value type: " << entry.min_val.type().name() << "\n"; return false; @@ -494,10 +494,10 @@ template bool CheckMaxValue(const OptionTestEntry& entry, SRTSOCKET sock, const char* desc) { try { - const ValueType max_val = linb::any_cast(entry.max_val); + const ValueType max_val = std::any_cast(entry.max_val); CheckSetSockOpt(entry, sock, max_val, SRT_SUCCESS, desc); } - catch (const linb::bad_any_cast&) + catch (const std::bad_any_cast&) { std::cerr << entry.optname << " max value type: " << entry.max_val.type().name() << "\n"; return false; @@ -513,10 +513,10 @@ bool CheckInvalidValues(const OptionTestEntry& entry, SRTSOCKET sock, const char { LOGD(cerr << "Will check INVALID value: " << entry.optname << " : " << to_string(inval) << ": " << sock_name << endl); try { - const ValueType val = linb::any_cast(inval); + const ValueType val = std::any_cast(inval); CheckSetSockOpt(entry, sock, val, SRT_ERROR, sock_name); } - catch (const linb::bad_any_cast&) + catch (const std::bad_any_cast&) { std::cerr << entry.optname << " value type: " << inval.type().name() << "\n"; return false; From 86bc2c97d6a0600faa4d55bcba9664873322d0a0 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Tue, 25 Nov 2025 14:19:14 +0100 Subject: [PATCH 11/26] Implemented per-packet busy flags and locker object for sending packets --- srtcore/buffer_snd.cpp | 357 ++++++++++++++++++++++----------------- srtcore/buffer_snd.h | 143 +++++++++------- srtcore/core.cpp | 45 ++--- srtcore/core.h | 8 +- srtcore/queue.cpp | 12 +- test/test_buffer_snd.cpp | 186 ++++++++++++++++++-- 6 files changed, 484 insertions(+), 267 deletions(-) diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 6a43f28ab..651273584 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -71,11 +71,11 @@ CSndBuffer::CSndBuffer(size_t bytesize, size_t slicesize, size_t mss, size_t hea m_iBlockLen(mss - headersize), m_iReservedSize(reservedsize), m_iSndLastDataAck(SRT_SEQNO_NONE), + m_iSndUpdateAck(SRT_SEQNO_NONE), m_iNextMsgNo(1), m_iBytesCount(0), #if SRT_SNDBUF_NEW m_iCapacity(number_slices(bytesize, m_iBlockLen)), - m_iSndReservedSeq(SRT_SEQNO_NONE), // To avoid performance degradation during the transmission, // we allocate in advance all required blocks so that they are // picked up from the storage when required. @@ -102,7 +102,7 @@ CSndBuffer::CSndBuffer(size_t bytesize, size_t slicesize, size_t mss, size_t hea m_rateEstimator.setHeaderSize(headersize); initialize(); - setupMutex(m_BufLock, "Buf"); + setupMutex(m_BufLock, "SndBuf"); } #if SRT_SNDBUF_NEW @@ -123,7 +123,9 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) ScopedLock bufferguard(m_BufLock); if (m_iSndLastDataAck == SRT_SEQNO_NONE) - m_iSndLastDataAck = w_seqno; + { + m_iSndLastDataAck = m_iSndUpdateAck = w_seqno; + } HLOGC(bslog.Debug, log << "addBuffer: needs=" << iNumBlocks << " buffers for " << len << " bytes. Taken=" @@ -157,7 +159,7 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) // This will never fail; it is believed that if the buffer size reached // defined capacity, addBuffer will not be called. - PacketContainer::Packet& p = m_Packets.push(); + SndPktArray::Packet& p = m_Packets.push(); HLOGC(bslog.Debug, log << "addBuffer: %" << w_seqno << " #" << w_msgno << " offset=" << (i * iPktLen) @@ -216,7 +218,7 @@ int CSndBuffer::addBufferFromFile(fstream& ifs, int len) if (pktlen > iPktLen) pktlen = iPktLen; - PacketContainer::Packet& p = m_Packets.push(); + SndPktArray::Packet& p = m_Packets.push(); HLOGC(bslog.Debug, log << "addBufferFromFile: reading from=" << (i * iPktLen) << " size=" << pktlen @@ -249,22 +251,22 @@ int CSndBuffer::addBufferFromFile(fstream& ifs, int len) return total; } -PacketContainer::Packet* PacketContainer::get_unique() +SndPktArray::Packet* SndPktArray::extract_unique() { // It should be only possible to be 0, but just in case. if (m_iNewQueued <= 0) return NULL; - SRT_ASSERT(m_iNewQueued <= int(m_Container.size())); + SRT_ASSERT(m_iNewQueued <= int(m_PktQueue.size())); // If m_iNewQueued == 1, then only the last item is the unique one, // which's index is size()-1. - size_t index = int(m_Container.size()) - m_iNewQueued; + size_t index = int(m_PktQueue.size()) - m_iNewQueued; --m_iNewQueued; // We checked in advance that it's > 0. - return &m_Container[index]; + return &m_PktQueue[index]; } -int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc) +int CSndBuffer::extractUniquePacket(CSndPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc) { int readlen = 0; w_seqnoinc = 0; @@ -274,7 +276,7 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, // In the block there will be skipped the TTL-expired messages, if any. for (;;) { - PacketContainer::Packet* p = m_Packets.get_unique(); + SndPktArray::Packet* p = m_Packets.extract_unique(); if (!p) return 0; @@ -294,17 +296,17 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, } // Make the packet REFLECT the data stored in the buffer. - w_packet.m_pcData = p->m_pcData; + w_packet.pkt.m_pcData = p->m_pcData; readlen = p->m_iLength; - w_packet.setLength(readlen, m_iBlockLen); - w_packet.set_seqno(p->m_iSeqNo); + w_packet.pkt.setLength(readlen, m_iBlockLen); + w_packet.pkt.set_seqno(p->m_iSeqNo); // 1. On submission (addBuffer), the KK flag is set to EK_NOENC (0). - // 2. The readData() is called to get the original (unique) payload not ever sent yet. + // 2. The extractUniquePacket() is called to get the original (unique) payload not ever sent yet. // The payload must be encrypted for the first time if the encryption // is enabled (arg kflgs != EK_NOENC). The KK encryption flag of the data packet // header must be set and remembered accordingly (see EncryptionKeySpec). - // 3. The next time this packet is read (only for retransmission), the payload is already + // 3. The next time this packet is read (readOldPacket), the payload is already // encrypted, and the proper flag value is already stored. // TODO: Alternatively, encryption could happen before the packet is submitted to the buffer @@ -321,18 +323,16 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, { p->m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(kflgs); } + w_packet.pkt.set_msgflags(p->m_iMsgNoBitset); - // Reserve this seqno to persist for the time when this packet is used - // for being sent, until it's released. - if (m_iSndReservedSeq == SRT_SEQNO_NONE || SeqNo(p->m_iSeqNo) < SeqNo(m_iSndReservedSeq)) - { - m_iSndReservedSeq = p->m_iSeqNo; - } + // Also make THIS packet busy. + ++p->m_iBusy; + w_packet.acquire_busy(p->m_iSeqNo, this); HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: UNIQUE packet to send: size=" << readlen - << " #" << w_packet.getMsgSeq() - << " %" << w_packet.seqno() - << " !" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); + << " #" << w_packet.pkt.getMsgSeq() + << " %" << w_packet.pkt.seqno() + << " !" << BufferStamp(w_packet.pkt.m_pcData, w_packet.pkt.getLength())); break; } @@ -371,14 +371,14 @@ int32_t CSndBuffer::getMsgNoAtSeq(const int32_t seqno) return m_Packets[offset].getMsgSeq(); } -int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) +int CSndBuffer::readOldPacket(int32_t seqno, CSndPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) { ScopedLock bufferguard(m_BufLock); int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); if (offset < 0 || offset >= int(m_Packets.size())) { - LOGC(qslog.Error, log << "CSndBuffer::readOldPacket: for %" << seqno << " offset " << offset << " out of buffer (earliest: %" + LOGC(bslog.Error, log << "CSndBuffer::readOldPacket: for %" << seqno << " offset " << offset << " out of buffer (earliest: %" << m_iSndLastDataAck << ")!"); return READ_NONE; } @@ -386,18 +386,18 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti // Unlike receiver buffer, in the sender buffer packets are always stored // one after another and there are no gaps. Checking the valid range of offset // suffices to grant existence of a packet. - PacketContainer::Packet* p = &m_Packets[offset]; + SndPktArray::Packet* p = &m_Packets[offset]; #if HVU_ENABLE_HEAVY_LOGGING const int32_t first_seq = p->m_iSeqNo; int32_t last_seq = p->m_iSeqNo; #endif - w_packet.set_seqno(seqno); + w_packet.pkt.set_seqno(seqno); // This is rexmit request, so the packet should have the sequence number // already set when it was once sent uniquely. - SRT_ASSERT(p->m_iSeqNo == w_packet.seqno()); + SRT_ASSERT(p->m_iSeqNo == w_packet.pkt.seqno()); // Check if the block that is the next candidate to send (m_pCurrBlock pointing) is stale. @@ -423,8 +423,8 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti lastx = i; } - HLOGC(qslog.Debug, - log << "CSndBuffer::readData: due to TTL exceeded, %(" << first_seq << " - " << last_seq << "), " + HLOGC(bslog.Debug, + log << "CSndBuffer::extractUniquePacket: due to TTL exceeded, %(" << first_seq << " - " << last_seq << "), " << (1 + lastx - offset) << " packets to drop with #" << w_drop.msgno); // Make sure that the packets belonging to the expired message are @@ -432,8 +432,8 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti m_Packets.set_expired(lastx); w_drop.msgno = p->getMsgSeq(); - w_drop.seqno[DropRange::BEGIN] = w_packet.seqno(); - w_drop.seqno[DropRange::END] = CSeqNo::incseq(w_packet.seqno(), lastx - offset); + w_drop.seqno[DropRange::BEGIN] = w_packet.pkt.seqno(); + w_drop.seqno[DropRange::END] = CSeqNo::incseq(w_packet.pkt.seqno(), lastx - offset); // We let the caller handle it, while we state no packet delivered. // NOTE: the expiration of a message doesn't imply recovation from the buffer. @@ -441,29 +441,25 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti return READ_DROP; } - w_packet.m_pcData = p->m_pcData; + w_packet.pkt.m_pcData = p->m_pcData; const int readlen = p->m_iLength; - w_packet.setLength(readlen, m_iBlockLen); + w_packet.pkt.setLength(readlen, m_iBlockLen); // We state that the requested seqno refers to a historical (not unique) // packet, hence the encryption action has encrypted the data and updated // the flags. - w_packet.set_msgflags(p->m_iMsgNoBitset); + w_packet.pkt.set_msgflags(p->m_iMsgNoBitset); w_srctime = p->m_tsOriginTime; // This function is called when packet retransmission is triggered. // Therefore we are setting the rexmit time. p->m_tsRexmitTime = steady_clock::now(); - // Reserve this seqno to persist for the time when this packet is used - // for being sent, until it's released. - if (m_iSndReservedSeq == SRT_SEQNO_NONE || SeqNo(p->m_iSeqNo) < SeqNo(m_iSndReservedSeq)) - { - m_iSndReservedSeq = p->m_iSeqNo; - } + ++p->m_iBusy; + w_packet.acquire_busy(p->m_iSeqNo, this); - HLOGC(qslog.Debug, - log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.seqno() + HLOGC(bslog.Debug, + log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.pkt.seqno() << " size=" << readlen << " to send [REXMIT]"); return readlen; @@ -480,62 +476,49 @@ sync::steady_clock::time_point CSndBuffer::getRexmitTime(const int32_t seqno) return m_Packets[offset].m_tsRexmitTime; } -bool CSndBuffer::reserveSeqno(int32_t seq) +void CSndBuffer::releasePacket(int32_t seqno) { ScopedLock bufferguard(m_BufLock); - int offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); - // IF distance between m_iSndLastDataAck and ack is nonempty... + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); if (offset < 0 || offset >= int(m_Packets.size())) - return false; - - // If there exists any previous reservation, do not - // overwrite it. - if (m_iSndReservedSeq != SRT_SEQNO_NONE && SeqNo(m_iSndReservedSeq) < SeqNo(seq)) - return true; - - m_iSndReservedSeq = seq; - return true; -} - -bool CSndBuffer::releaseSeqno(int32_t new_ack_seq) -{ - ScopedLock bufferguard(m_BufLock); - if (m_iSndReservedSeq == SRT_SEQNO_NONE) { - // We state there is no reservation, so m_iSndLastDataAck == new_ack_seq. - return false; + // XXX Issue a log; should never happen + // (a packet shall not be removed from the sender buffer if it is + // busy, no matter for what reason it's attempted to be removed) + return; } - // Unreserve, regardless of the value of new_ack_seq. We need that - // since this moment the buffer can be freely revoked by ACK. - m_iSndReservedSeq = SRT_SEQNO_NONE; - - int offset = CSeqNo::seqoff(m_iSndLastDataAck, new_ack_seq); - if (offset <= 0) // new_ack_seq doesn't succeed any packets in the buffer - return false; - - // Now continue with revoke. Not calling revoke() due to - // mutex complications. - m_Packets.pop(offset); + if (m_Packets[offset].m_iBusy <= 0) + { + // XXX Issue a log - this is OOS or memover case. + return; + } - m_iSndLastDataAck = new_ack_seq; + --m_Packets[offset].m_iBusy; - updAvgBufSize(steady_clock::now()); - return true; + // After releasing this packet try to revoke as many packets + // as possible, up to m_iSndUpdateAck. + IF_HEAVY_LOGGING(bool logged = false); + if (m_iSndUpdateAck != SRT_SEQNO_NONE && m_iSndUpdateAck != m_iSndLastDataAck) + { + int latest_offset = CSeqNo::seqoff(m_iSndLastDataAck, m_iSndUpdateAck); + if (latest_offset > 0) + { + int removed = m_Packets.pop(latest_offset); + m_iSndLastDataAck = CSeqNo::incseq(m_iSndLastDataAck, removed); + HLOGC(bslog.Debug, log << "CSndBuffer::releasePacket %" << seqno << ": ACK-revoked " << removed + << " more packets up to %" << m_iSndLastDataAck); + logged = true; + } + } + IF_HEAVY_LOGGING(if (!logged) LOGC(bslog.Debug, log << "CSndBuffer::releasePacket: %" << seqno << ": non+ pkts revoked")); } bool CSndBuffer::revoke(int32_t seqno) { ScopedLock bufferguard(m_BufLock); - // If there exists reservation marker, check if this is going to be - // released. If so, keep all packets up to the reserved position. - if (m_iSndReservedSeq != SRT_SEQNO_NONE && SeqNo(seqno) > SeqNo(m_iSndReservedSeq)) - { - seqno = m_iSndReservedSeq; - } - int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); // IF distance between m_iSndLastDataAck and ack is nonempty... @@ -545,32 +528,44 @@ bool CSndBuffer::revoke(int32_t seqno) // NOTE: offset points to the first packet that should remain // in the buffer, hence it's already a past-the-end for the revoked. // The call is also safe for calling with excessive value of offset. - m_Packets.pop(offset); - - // We don't check if the sequence is past the last scheduled one; - // worst case scenario we'll just clear up the whole buffer. - m_iSndLastDataAck = seqno; + int popped_up_to = m_Packets.pop(offset); + if (popped_up_to == offset) + { + m_iSndLastDataAck = seqno; + m_iSndUpdateAck = seqno; + HLOGC(bslog.Debug, log << "CSndBuffer::revoke: all up to ACK %" << seqno); + } + else + { + // We have removed less packets than required because some were + // currently reserved as busy, therefore only remember the original + // sequence number so that these packets are removed later. + m_iSndUpdateAck = seqno; + m_iSndLastDataAck = CSeqNo::incseq(m_iSndLastDataAck, popped_up_to); + HLOGC(bslog.Debug, log << "CSndBuffer::revoke: ONLY UP TO first busy %" << m_iSndLastDataAck + << " with postponed ACK %" << m_iSndUpdateAck); + } updAvgBufSize(steady_clock::now()); return true; } -// NOTE: 'n' is the index in the m_Container array +// NOTE: 'n' is the index in the m_PktQueue array // up to which (including) the losses must be cleared off. // This should only result in making the m_iFirstRexmit // and m_iLastRexmit field pointing to either -1 or // valid indexes in the container, but OUTSIDE the range // from 0 to n. -void PacketContainer::remove_loss(int last_to_clear) +void SndPktArray::remove_loss(int last_to_clear) { // This is going to remove the loss records since the first one // up to the packet designated by the last_to_clear offset (same as pop()). // this empty() is just formally - with empty m_iFirstRexmit should be moreover -1 - if (m_iFirstRexmit == -1 || m_Container.empty()) // no loss records + if (m_iFirstRexmit == -1 || m_PktQueue.empty()) // no loss records return; // last is also -1 in this situation - const int LASTX = int(m_Container.size()) - 1; + const int LASTX = int(m_PktQueue.size()) - 1; // Handle special case: if last_to_clear is the last index in the container, // simply remove everything. Just update all the loss nodes. @@ -580,7 +575,7 @@ void PacketContainer::remove_loss(int last_to_clear) { // use safe-loop rule because the node data will be cleared here. next = next_loss(loss); - PacketContainer::Packet& p = m_Container[loss]; + SndPktArray::Packet& p = m_PktQueue[loss]; p.m_iLossLength = 0; p.m_iNextLossGroupOffset = 0; } @@ -615,30 +610,30 @@ void PacketContainer::remove_loss(int last_to_clear) // Ride until you find a split-in-half record, // a new record beyond last_to_clear, or no more records. - PacketContainer::Packet& p = m_Container[m_iFirstRexmit]; + SndPktArray::Packet& p = m_PktQueue[m_iFirstRexmit]; int last_index = m_iFirstRexmit + p.m_iLossLength - 1; if (last_to_clear < last_index) { // split-in-half case. This will be the last on which the operation is done. - int new_beginning = last_to_clear + 1; // The case when last_to_clear == m_Container.size() - 1 is handled already + int new_beginning = last_to_clear + 1; // The case when last_to_clear == m_PktQueue.size() - 1 is handled already int revoked_length_fragment = new_beginning - m_iFirstRexmit; // Now shift the position bool is_last = false; - m_Container[new_beginning].m_iLossLength = p.m_iLossLength - revoked_length_fragment; + m_PktQueue[new_beginning].m_iLossLength = p.m_iLossLength - revoked_length_fragment; if (p.m_iNextLossGroupOffset) { int next_index = m_iFirstRexmit + p.m_iNextLossGroupOffset; // Replicate the distance at the new index - m_Container[new_beginning].m_iNextLossGroupOffset = next_index - new_beginning; + m_PktQueue[new_beginning].m_iNextLossGroupOffset = next_index - new_beginning; } else { // No next group, this is the last one. - m_Container[new_beginning].m_iNextLossGroupOffset = 0; + m_PktQueue[new_beginning].m_iNextLossGroupOffset = 0; is_last = true; } @@ -647,7 +642,7 @@ void PacketContainer::remove_loss(int last_to_clear) p.m_iNextLossGroupOffset = 0; // NOTE: the new values of m_iFirstRexmit and m_iLastRexmit set here - // are valid indexes AFTER REMOVAL of the revoked elements from m_Container. + // are valid indexes AFTER REMOVAL of the revoked elements from m_PktQueue. m_iFirstRexmit = new_beginning; if (is_last) m_iLastRexmit = m_iFirstRexmit; @@ -692,11 +687,11 @@ bool CSndBuffer::cancelLostSeq(int32_t seq) return m_Packets.clear_loss(offset); } -bool PacketContainer::clear_loss(int index) +bool SndPktArray::clear_loss(int index) { // Just access the record. Now return false only // if it turns out that it has never been rexmit-scheduled. - PacketContainer::Packet& p = m_Container[index]; + SndPktArray::Packet& p = m_PktQueue[index]; if (is_zero(p.m_tsNextRexmitTime)) return false; @@ -704,41 +699,70 @@ bool PacketContainer::clear_loss(int index) return true; } -void PacketContainer::clear() +void SndPktArray::clear() { // pop() will do erase(begin(), end()) in this case, // but will also destroy every packet. pop(size()); } +SndPktArray::Packet& SndPktArray::push() +{ + m_PktQueue.push_back(Packet()); + m_iCachedSize = m_PktQueue.size(); + + Packet& that = m_PktQueue.back(); + + // Allocate the packet payload space + that.m_iBusy = 0; + that.m_iLength = m_Storage.blocksize; + that.m_pcData = m_Storage.get(); + + // pushing is always treated as adding a new unique packet + ++m_iNewQueued; + + // Return as is - without initialized fields. + return that; +} + // 'n' is the past-the-end index for removal -size_t PacketContainer::pop(size_t n) +size_t SndPktArray::pop(size_t n) { - if (m_Container.empty() || !n) + if (m_PktQueue.empty() || !n) return 0; // The size is also unchanged - if (n > m_Container.size()) - n = m_Container.size(); + if (n > m_PktQueue.size()) + n = m_PktQueue.size(); // We consider that this call clears off losses in the container // calls from 0 to n (inc). + + // NOTE: Losses are removed anyway, regardless of the busy status. remove_loss(n-1); // remove_loss includes given index - deque::iterator i = m_Container.begin(), upto = i + n; + deque::iterator i = m_PktQueue.begin(), upto = i + n; for (; i != upto; ++i) { + // Stop at first busy. + if (i->m_iBusy) + { + //prematurely interrupted; update n. + upto = i; + n = std::distance(m_PktQueue.begin(), upto); + break; + } // Deallocate storage m_Storage.put(i->m_pcData); } - m_Container.erase(m_Container.begin(), upto); - m_iCachedSize = m_Container.size(); + m_PktQueue.erase(m_PktQueue.begin(), upto); + m_iCachedSize = m_PktQueue.size(); // pop might have removed also packets from the unique range; // in that case just shrink it to the existing range. - m_iNewQueued = std::min(m_iNewQueued, m_Container.size()); + m_iNewQueued = std::min(m_iNewQueued, m_PktQueue.size()); - // These are indexes into the m_Container container, so with + // These are indexes into the m_PktQueue container, so with // removed n elements, their position in the container also // get shifted by n. if (m_iFirstRexmit != -1) @@ -755,10 +779,10 @@ size_t PacketContainer::pop(size_t n) return n; } -bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point& next_rexmit_time) +bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& next_rexmit_time) { // Can't install loss to an empty container - if (m_Container.empty()) + if (m_PktQueue.empty()) return false; // Fix the indexes if they are out of bound. Note that they can potentially @@ -766,7 +790,7 @@ bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point // not much we can do about it). Check only if this really is lo-hi relationship // and whether at least a fragment of the range is in the buffer. - if (offset_lo > offset_hi || offset_hi < 0 || offset_lo >= int(m_Container.size())) + if (offset_lo > offset_hi || offset_hi < 0 || offset_lo >= int(m_PktQueue.size())) return false; if (offset_lo < 0) @@ -777,10 +801,10 @@ bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point } // It was checked that size() is at least 1 - if (offset_hi >= int(m_Container.size())) + if (offset_hi >= int(m_PktQueue.size())) { - //int fix = offset_hi - m_Container.size(); - offset_hi = m_Container.size() - 1; + //int fix = offset_hi - m_PktQueue.size(); + offset_hi = m_PktQueue.size() - 1; //seqhi = CSeqNo::decseq(seqhi, fix); } @@ -793,7 +817,7 @@ bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point if (m_iFirstRexmit == -1) { // Add just one record and mark in both. - PacketContainer::Packet& p = m_Container[offset_lo]; + SndPktArray::Packet& p = m_PktQueue[offset_lo]; p.m_iNextLossGroupOffset = 0; p.m_iLossLength = loss_length; m_iFirstRexmit = m_iLastRexmit = offset_lo; @@ -811,7 +835,7 @@ bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point // or past the last record. What we know for sure is that it // doesn't overlap with any earlier loss records. - PacketContainer::Packet& butlast = m_Container[m_iLastRexmit]; + SndPktArray::Packet& butlast = m_PktQueue[m_iLastRexmit]; // Still, the record can overlap. int last_end_ix = m_iLastRexmit + butlast.m_iLossLength; // past-the-end! @@ -833,7 +857,7 @@ bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point } // No overlaps, just add the new last one. - PacketContainer::Packet& last = m_Container[offset_lo]; + SndPktArray::Packet& last = m_PktQueue[offset_lo]; int butlast_distance = offset_lo - m_iLastRexmit; @@ -856,7 +880,7 @@ bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point // - overlapping or interleaving with existing records, EXCEPT the last one. // (still may overlap with the last record, that is, extend its beginning) - // --- PacketContainer::Packet& p = m_Container[m_iFirstRexmit]; + // --- SndPktArray::Packet& p = m_PktQueue[m_iFirstRexmit]; vector index_to_clear; // Current form: @@ -888,7 +912,7 @@ bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point // first will be its next. if (offset_hi < m_iFirstRexmit) { - PacketContainer::Packet& first = m_Container[offset_lo]; + SndPktArray::Packet& first = m_PktQueue[offset_lo]; first.m_iLossLength = loss_length; first.m_iNextLossGroupOffset = m_iFirstRexmit - offset_lo; m_iFirstRexmit = offset_lo; @@ -907,7 +931,7 @@ bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point // skip all records that whole precede the inserted record. for (int loss_index = m_iFirstRexmit; loss_index != -1; loss_index = next_loss(loss_index)) { - PacketContainer::Packet& ip = m_Container[loss_index]; + SndPktArray::Packet& ip = m_PktQueue[loss_index]; int loss_end = loss_index + ip.m_iLossLength; if (loss_end >= offset_lo) @@ -942,7 +966,7 @@ bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point // All indices in the middle should be cleared index_to_clear.push_back(loss_index); - eclipsed_length += m_Container[loss_index].m_iLossLength; + eclipsed_length += m_PktQueue[loss_index].m_iLossLength; } // Now its: @@ -960,17 +984,17 @@ bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point for (size_t i = 0; i < index_to_clear.size(); ++i) { int x = index_to_clear[i]; - int hi = x + m_Container[x].m_iLossLength - 1; - m_Container[x].m_iLossLength = 0; - m_Container[x].m_iNextLossGroupOffset = 0; + int hi = x + m_PktQueue[x].m_iLossLength - 1; + m_PktQueue[x].m_iLossLength = 0; + m_PktQueue[x].m_iNextLossGroupOffset = 0; furthest_hi = std::max(furthest_hi, hi); } if (lowest_lo != offset_lo) { // Clear the packet if it wasn't at the border - m_Container[lowest_lo].m_iNextLossGroupOffset = 0; - m_Container[lowest_lo].m_iLossLength = 0; + m_PktQueue[lowest_lo].m_iNextLossGroupOffset = 0; + m_PktQueue[lowest_lo].m_iLossLength = 0; } else { @@ -978,15 +1002,15 @@ bool PacketContainer::insert_loss(int offset_lo, int offset_hi, const time_point } loss_length = furthest_hi - lowest_lo + 1; - m_Container[lowest_lo].m_iLossLength = loss_length; - m_Container[lowest_lo].m_iNextLossGroupOffset = next_succeeding; + m_PktQueue[lowest_lo].m_iLossLength = loss_length; + m_PktQueue[lowest_lo].m_iNextLossGroupOffset = next_succeeding; int new_length = m_iLossLengthCache - eclipsed_length + loss_length; if (last_preceding_index == -1) m_iFirstRexmit = lowest_lo; else - m_Container[last_preceding_index].m_iNextLossGroupOffset = lowest_lo; + m_PktQueue[last_preceding_index].m_iNextLossGroupOffset = lowest_lo; m_iLossLengthCache = new_length; // Set the rexmit time only to the range that was requested to be inserted, @@ -1016,7 +1040,7 @@ int32_t CSndBuffer::popLostSeq(DropRange& w_drop) return seqno; } -int PacketContainer::extractFirstLoss() +int SndPktArray::extractFirstLoss() { // No loss at all if (m_iFirstRexmit == -1) @@ -1038,11 +1062,11 @@ int PacketContainer::extractFirstLoss() // Walk over the container to find the valid loss sequence for (int loss_begin = m_iFirstRexmit; loss_begin != -1; loss_begin = next_loss(loss_begin)) { - int loss_end = loss_begin + m_Container[loss_begin].m_iLossLength; + int loss_end = loss_begin + m_PktQueue[loss_begin].m_iLossLength; for (int i = loss_begin; i != loss_end; ++i) { - PacketContainer::Packet& p = m_Container[i]; + SndPktArray::Packet& p = m_PktQueue[i]; if (!is_zero(p.m_tsNextRexmitTime)) { // Ok, so this cell will be taken, but it might be the future. @@ -1174,15 +1198,21 @@ int CSndBuffer::dropLateData(int& w_bytes, int32_t& w_first_msgno, const steady_ // Reach out to the position that is less than too_late_time, // counting the bytes size_t i; - for (i = 0; i < m_Packets.size() && m_Packets[i].m_tsOriginTime < too_late_time; ++i) + for (i = 0; i < m_Packets.size(); ++i) { - dbytes += m_Packets[i].m_iLength; - msgno = m_Packets[i].getMsgSeq(); + SndPktArray::Packet& p = m_Packets[i]; + // Stop on first busy or young enough + if (p.m_iBusy || p.m_tsOriginTime >= too_late_time) + break; + dbytes += p.m_iLength; + msgno = p.getMsgSeq(); } // Now delete all these packets from the container if (i) { + // As the loop stopped on first busy, we ignore the return value + // because there should be no busy packets in this range. m_Packets.pop(i); const int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, i); @@ -1235,7 +1265,7 @@ string CSndBuffer::show() const ScopedLock bufferguard(m_BufLock); - PacketContainer::PacketShowState st; + SndPktArray::PacketShowState st; for (size_t i = 0; i < m_Packets.size(); ++i) { int seqno = CSeqNo::incseq(m_iSndLastDataAck, i); @@ -1247,9 +1277,9 @@ string CSndBuffer::show() const return out.str(); } -void PacketContainer::showline(int index, PacketShowState& st, hvu::ofmtbufstream& out) const +void SndPktArray::showline(int index, PacketShowState& st, hvu::ofmtbufstream& out) const { - const Packet& p = m_Container[index]; + const Packet& p = m_PktQueue[index]; if (is_zero(st.begin_time)) st.begin_time = steady_clock::now(); @@ -1303,6 +1333,11 @@ void PacketContainer::showline(int index, PacketShowState& st, hvu::ofmtbufstrea { out << " NEW"; } + + if (p.m_iBusy) + { + out << " <" << p.m_iBusy << ">"; + } } #else @@ -1481,7 +1516,7 @@ int CSndBuffer::addBufferFromFile(fstream& ifs, int len) return total; } -int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc) +int CSndBuffer::extractUniquePacket(CSndPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc) { int readlen = 0; w_seqnoinc = 0; @@ -1490,13 +1525,13 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, while (m_pCurrBlock != m_pLastBlock) { // Make the packet REFLECT the data stored in the buffer. - w_packet.m_pcData = m_pCurrBlock->m_pcData; + w_packet.pkt.m_pcData = m_pCurrBlock->m_pcData; readlen = m_pCurrBlock->m_iLength; - w_packet.setLength(readlen, m_iBlockLen); - w_packet.set_seqno(m_pCurrBlock->m_iSeqNo); + w_packet.pkt.setLength(readlen, m_iBlockLen); + w_packet.pkt.set_seqno(m_pCurrBlock->m_iSeqNo); // 1. On submission (addBuffer), the KK flag is set to EK_NOENC (0). - // 2. The readData() is called to get the original (unique) payload not ever sent yet. + // 2. The extractUniquePacket() is called to get the original (unique) payload not ever sent yet. // The payload must be encrypted for the first time if the encryption // is enabled (arg kflgs != EK_NOENC). The KK encryption flag of the data packet // header must be set and remembered accordingly (see EncryptionKeySpec). @@ -1519,7 +1554,7 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, } Block* p = m_pCurrBlock; - w_packet.set_msgflags(m_pCurrBlock->m_iMsgNoBitset); + w_packet.pkt.set_msgflags(m_pCurrBlock->m_iMsgNoBitset); w_srctime = m_pCurrBlock->m_tsOriginTime; m_pCurrBlock = m_pCurrBlock->m_pNext; @@ -1532,9 +1567,12 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, continue; } + w_packet.seqno = w_packet.pkt.seqno(); + w_packet.srcbuf = this; + HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: picked up packet to send: size=" << readlen - << " #" << w_packet.getMsgSeq() - << " %" << w_packet.seqno() + << " #" << w_packet.pkt.getMsgSeq() + << " %" << w_packet.pkt.seqno() << " !" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); break; @@ -1543,6 +1581,11 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, return readlen; } +void CSndBuffer::releasePacket(int32_t seqno) +{ + // only debug +} + CSndBuffer::time_point CSndBuffer::peekNextOriginal() const { ScopedLock bufferguard(m_BufLock); @@ -1615,7 +1658,7 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti } if (p == m_pLastBlock) { - LOGC(qslog.Error, log << "CSndBuffer::readData: offset " << offset << " too large!"); + LOGC(bslog.Error, log << "CSndBuffer::extractUniquePacket: offset " << offset << " too large!"); return READ_NONE; } #if HVU_ENABLE_HEAVY_LOGGING @@ -1663,8 +1706,8 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti msglen++; } - HLOGC(qslog.Debug, - log << "CSndBuffer::readData: due to TTL exceeded, %(" << first_seq << " - " << last_seq << "), " + HLOGC(bslog.Debug, + log << "CSndBuffer::extractUniquePacket: due to TTL exceeded, %(" << first_seq << " - " << last_seq << "), " << msglen << " packets to drop with #" << w_drop.msgno); // Theoretically as the seq numbers are being tracked, you should be able @@ -1693,7 +1736,7 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti // As this function is predicted to extract the data to send as a rexmited packet, // the packet must be in the form ready to send - so, in case of encryption, // encrypted, and with all ENC flags already set. So, the first call to send - // the packet originally (readData) must set these flags first. + // the packet originally (extractUniquePacket) must set these flags first. w_packet.set_msgflags(p->m_iMsgNoBitset); w_srctime = p->m_tsOriginTime; @@ -1701,7 +1744,7 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti // Therefore we are setting the rexmit time. p->m_tsRexmitTime = steady_clock::now(); - HLOGC(qslog.Debug, + HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.seqno() << " size=" << readlen << " to send [REXMIT]"); diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index f7e76a67f..8fc691ecb 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -76,6 +76,8 @@ modified by namespace srt { +class CSndBuffer; + struct CSndBlock { typedef sync::steady_clock::time_point time_point; @@ -100,10 +102,12 @@ struct CSndBlock #if SRT_SNDBUF_NEW -struct PacketContainer +struct SndPktArray { typedef sync::steady_clock::time_point time_point; + // Note: this structure has no constructor, so fields must be updated + // upon creation. Currently only m_PktQueue.push_back() calls do this. struct Packet: CSndBlock { // Defines the time of the next retransmission. @@ -130,6 +134,10 @@ struct PacketContainer int m_iLossLength; int m_iNextLossGroupOffset; + + // Busy flag. This is set by the extractor to mark this cell + // as must-stay, until it's cleared. + int m_iBusy; }; private: @@ -138,15 +146,15 @@ struct PacketContainer BufferedMessageStorage m_Storage; /// Container for the packets; managed internally with data consistency. - std::deque m_Container; + std::deque m_PktQueue; - /// Kept in sync with m_Container.size() to allow calling size() without locking. + /// Kept in sync with m_PktQueue.size() to allow calling size() without locking. sync::atomic m_iCachedSize; /// Distance between the newly stored packet and the end of container. /// This counts the number of packets since the push-end that were not /// yet sent as unique. Shifted by 1 (that is, decreased) with every packet - /// extracted by readData(). If 0, there are no new unique packets. + /// extracted by extractUniquePacket(). If 0, there are no new unique packets. int m_iNewQueued; /// This field keeps the index of the first packet that has an active @@ -219,7 +227,7 @@ struct PacketContainer public: - PacketContainer(size_t payload_len, size_t max_packets): + SndPktArray(size_t payload_len, size_t max_packets): m_Storage(payload_len, max_packets), m_iCachedSize(0), m_iNewQueued(0), @@ -227,6 +235,7 @@ struct PacketContainer m_iLastRexmit(-1), m_iLossLengthCache(0) { + m_Storage.reserve(max_packets); } int unique_size() const { return m_iNewQueued; } @@ -234,35 +243,18 @@ struct PacketContainer // GET, means the packet is still in the container, you just get access to it. // This call however changes the status of the retrieved packet by removing it // from the unique range and moving it to the history range. - Packet* get_unique(); + Packet* extract_unique(); void set_expired(int upindex) { - int remain_unique = m_Container.size() - upindex; + int remain_unique = m_PktQueue.size() - upindex; // if remain_unique > m_iNewQueued, it means that packets up to upindex // are already expired. m_iNewQueued = std::min(m_iNewQueued, remain_unique); } - Packet& push() - { - m_Container.push_back(Packet()); - m_iCachedSize = m_Container.size(); - - Packet& that = m_Container.back(); - - // Allocate the packet payload space - that.m_iLength = m_Storage.blocksize; - that.m_pcData = m_Storage.get(); - - // pushing is always treated as adding a new unique packet - ++m_iNewQueued; - - // Return as is - without initialized fields. - return that; - } - + Packet& push(); size_t pop(size_t n = 1); void remove_loss(int n); @@ -277,8 +269,8 @@ struct PacketContainer for (int i = ixlo; i <= ixhi; ++i) { // Do not override existing time value, only set anew if 0 - if (is_zero(m_Container[i].m_tsNextRexmitTime)) - m_Container[i].m_tsNextRexmitTime = time; + if (is_zero(m_PktQueue[i].m_tsNextRexmitTime)) + m_PktQueue[i].m_tsNextRexmitTime = time; } } @@ -287,7 +279,7 @@ struct PacketContainer if (current_loss == -1) return -1; - Packet& p = m_Container[current_loss]; + Packet& p = m_PktQueue[current_loss]; if (p.m_iNextLossGroupOffset == 0) return -1; // The last loss @@ -307,9 +299,10 @@ struct PacketContainer void clear(); // NOTE: operator[] is unchecked. Use indirectly. - Packet& operator[](size_t index) { return m_Container[index]; } - const Packet& operator[](size_t index) const { return m_Container[index]; } + Packet& operator[](size_t index) { return m_PktQueue[index]; } + const Packet& operator[](size_t index) const { return m_PktQueue[index]; } + // Helper state struct used in showline() only. struct PacketShowState { time_point begin_time; @@ -325,11 +318,40 @@ struct PacketContainer #else #endif +struct CSndPacket +{ + CPacket pkt; // real contents + CSndBuffer* srcbuf; // NULL if this object doesn't lock a packet + int32_t seqno; // seq representing the packet in sndbuf, or SRT_SEQNO_NONE if no packet + + CSndPacket(): srcbuf(NULL), seqno(SRT_SEQNO_NONE) {} + + // This function should be called by the sender buffer AFTER + // it has updated the busy flag, and still under buffer and ack lock. + void acquire_busy(int32_t seq, CSndBuffer* buf) + { + seqno = seq; + srcbuf = buf; + } + + // Release the binding, withdraw the busy flag from the sender + // buffer cell assigned to seqno, and try to revoke as much cells + // as possible, up to first busy and the registered last ACK. + void release(); + + ~CSndPacket() + { + release(); + } +}; + class CSndBuffer { typedef sync::steady_clock::time_point time_point; typedef sync::steady_clock::duration duration; + friend struct CSndPacket; + public: // XXX There's currently no way to access the socket ID set for // whatever the buffer is currently working for. Required to find @@ -364,6 +386,11 @@ class CSndBuffer /// - srctime: local time stamped on the packet (same as input, if input wasn't 0) /// - pktseq: sequence number to be stamped on the next packet /// - msgno: message number stamped on the packet + /// + /// IMPORTANT: all facilities that check the buffer size by getSndBufSize() + /// must be called in THE SAME THREAD as addBuffer(). And only this thread + /// should be allowed to add packets to this buffer. + /// /// @param [in] data pointer to the user data block. /// @param [in] len size of the block. /// @param [inout] w_mctrl Message control data @@ -377,18 +404,20 @@ class CSndBuffer SRT_TSA_NEEDS_NONLOCKED(m_BufLock) int addBufferFromFile(std::fstream& ifs, int len); - // Special values that can be returned by readData. + // Special values that can be returned by extractUniquePacket. static const int READ_NONE = 0; static const int READ_DROP = -1; - /// Find data position to pack a DATA packet from the furthest reading point. + /// Get access to the packet at the next unique position. The unique position + /// will be moved after extraction. + /// /// @param [out] packet the packet to read. /// @param [out] origintime origin time stamp of the message /// @param [in] kflags Odd|Even crypto key flag /// @param [out] seqnoinc the number of packets skipped due to TTL, so that seqno should be incremented. /// @return Actual length of data read. SRT_TSA_NEEDS_NONLOCKED(m_BufLock) - int readData(CPacket& w_packet, time_point& w_origintime, int kflgs, int& w_seqnoinc); + int extractUniquePacket(CSndPacket& w_packet, time_point& w_origintime, int kflgs, int& w_seqnoinc); /// Peek an information on the next original data packet to send. /// @return origin time stamp of the next packet; epoch start time otherwise. @@ -401,26 +430,9 @@ class CSndBuffer int32_t seqno[2]; int32_t msgno; }; - /// Find data position to pack a DATA packet for a retransmission. - /// IMPORTANT: @a packet is [in,out] because it is expected to get set - /// the sequence number of the packet expected to be sent next. The sender - /// buffer normally doesn't handle sequence numbers and the consistency - /// between the sequence number of a packet already sent and kept in the - /// buffer is achieved by having the sequence number recorded in the - /// CUDT::m_iSndLastDataAck field that should represent the oldest packet - /// still in the buffer. - /// @param [in] offset offset from the last ACK point (backward sequence number difference) - /// @param [in,out] w_packet storage for the packet, preinitialized with sequence number - /// @param [out] w_origintime origin time stamp of the message - /// @param [out] w_drop the drop information in case when dropping is to be done instead - /// @retval >0 Length of the data read. - /// @retval READ_NONE No data available or @a offset points out of the buffer occupied space. - /// @retval READ_DROP The call requested data drop due to TTL exceeded, to be handled first. - // SRT_TSA_NEEDS_NONLOCKED(m_BufLock) - // int readData(const int offset, CPacket& w_packet, time_point& w_origintime, DropRange& w_drop); SRT_TSA_NEEDS_NONLOCKED(m_BufLock) - int readOldPacket(int32_t seqno, CPacket& w_packet, time_point& w_origintime, DropRange& w_drop); + int readOldPacket(int32_t seqno, CSndPacket& w_packet, time_point& w_origintime, DropRange& w_drop); /// Get the time of the last retransmission (if any) of the DATA packet. /// @param [in] offset offset from the last ACK point (backward sequence number difference) @@ -484,7 +496,7 @@ class CSndBuffer int32_t firstSeqNo() const { return m_iSndLastDataAck; } // Required in group sequence override - void overrideFirstSeqNo(int32_t seq) { m_iSndLastDataAck = seq; } + void overrideFirstSeqNo(int32_t seq) { m_iSndLastDataAck = seq; m_iSndUpdateAck = SRT_SEQNO_NONE; } // Sender loss list management methods void removeLossUpTo(int32_t seqno); @@ -502,11 +514,6 @@ class CSndBuffer std::string show() const; - /// Reserves the sequence number that must prevail, even if - /// it's covered by ACK. - bool reserveSeqno(int32_t seq); - bool releaseSeqno(int32_t new_ack_seq); - private: void initialize(); @@ -522,7 +529,8 @@ class CSndBuffer const int m_iBlockLen; // maximum length of a block holding packet payload and AUTH tag (excluding packet header). const int m_iReservedSize; // Authentication tag size (if GCM is enabled). - sync::atomic m_iSndLastDataAck; // The real last ACK that updates the sender buffer and loss list + sync::atomic m_iSndLastDataAck; // seqno of the packet in cell [0]. + sync::atomic m_iSndUpdateAck; // seqno up to which the last ACK was received (%>= m_iSndLastDataAck) int32_t m_iNextMsgNo; // next message number int m_iBytesCount; // number of payload bytes in queue time_point m_tsLastOriginTime; @@ -530,17 +538,14 @@ class CSndBuffer AvgBufSize m_mavg; CRateEstimator m_rateEstimator; + void releasePacket(int32_t seqno); + #if SRT_SNDBUF_NEW /// Buffer capacity (maximum size), used intermediately and in initialization only. int m_iCapacity; - /// Reserved lowest sequence number that should guarantee being - /// still in the buffer, or SRT_SEQNO_NONE if no guarantee was requested. - /// The call to revoke will not remove these packets, even if succeeds this. - sync::atomic m_iSndReservedSeq; - - PacketContainer m_Packets; + SndPktArray m_Packets; int getBufferStats(int& bytes, int& timespan) const; @@ -585,6 +590,16 @@ class CSndBuffer CSndBuffer& operator=(const CSndBuffer&); }; +inline void CSndPacket::release() +{ + if (!srcbuf || seqno == SRT_SEQNO_NONE) + return; + + srcbuf->releasePacket(seqno); + seqno = SRT_SEQNO_NONE; + srcbuf = NULL; +} + } // namespace srt #endif diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 5f34ec7dc..c6b166236 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9764,7 +9764,7 @@ bool srt::CUDT::checkRexmitRightTime(int32_t seqno, const srt::sync::steady_cloc // [[using locked (m_RecvAckLock)]] -int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, CPacket& w_packet, srt::sync::steady_clock::time_point& w_tsOrigin) +int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, CSndPacket& w_packet, srt::sync::steady_clock::time_point& w_tsOrigin) { // REPEATABLE BLOCK (not a real loop) // The call to readOldPacket may result in a drop request, which must be @@ -9802,7 +9802,7 @@ int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, CPacket& w_packet, srt::s } -int CUDT::packLostData(CPacket& w_packet) +int CUDT::packLostData(CSndPacket& w_sndpkt) { #ifdef SRT_ENABLE_MAXREXMITBW @@ -9860,11 +9860,13 @@ int CUDT::packLostData(CPacket& w_packet) // Extract the packet from the sender buffer that is mapped to the expected sequence // number, bypassing and taking care of those that are decided to be dropped. - const int payload = extractCleanRexmitPacket(seqno, (w_packet), (tsOrigin)); + const int payload = extractCleanRexmitPacket(seqno, (w_sndpkt), (tsOrigin)); if (payload <= 0) return 0; } + CPacket& w_packet = w_sndpkt.pkt; + HLOGC(qslog.Debug, log << CONID() << "packed REXMIT packet %" << w_packet.seqno() << " size=" << w_packet.getLength() << " - still " << m_pSndBuffer->getLossLength() << " LOSS ENTRIES left"); @@ -9883,7 +9885,7 @@ int CUDT::packLostData(CPacket& w_packet) // Token consumption will only happen when the retransmission // effectively happens. // XXX NOTE: In version 1.6.0 use the IP-version dependent value for UDP_HDR_SIZE - size_t network_size = w_packet.getLength() + CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion); + size_t network_size = w_sndpkt.getLength() + CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion); m_SndRexmitShaper.consumeTokens(network_size); HLOGC(qslog.Debug, log << "REXMIT-SH: consumed " << network_size << " tokens, remain " << m_SndRexmitShaper.ntokens()); } @@ -9894,7 +9896,7 @@ int CUDT::packLostData(CPacket& w_packet) leaveCS(m_StatsLock); // Despite the contextual interpretation of packet.m_iMsgNo around - // CSndBuffer::readData version 2 (version 1 doesn't return -1), in this particular + // CSndBuffer::readOldPacket (extractUniquePacket doesn't return -1), in this particular // case we can be sure that this is exactly the value of PH_MSGNO as a bitset. // So, set here the rexmit flag if the peer understands it. if (m_bPeerRexmitFlag) @@ -10154,16 +10156,7 @@ void CUDT::updateSenderMeasurements(bool can_rexmit SRT_ATR_UNUSED) } -bool CUDT::releaseSend() -{ - ScopedLock lkrack (m_RecvAckLock); - if (!m_pSndBuffer) - return false; - - return m_pSndBuffer->releaseSeqno(m_iSndLastAck); -} - -bool CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, CNetworkInterface& w_src_addr) +bool CUDT::packData(CSndPacket& w_sndpkt, steady_clock::time_point& w_nexttime, CNetworkInterface& w_src_addr) { int payload = 0; bool probe = false; @@ -10193,7 +10186,7 @@ bool CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, CNe // for being sent and sending this packet has a prioriry over retransmission candidate. if (!isRegularSendingPriority()) { - payload = packLostData((w_packet)); + payload = packLostData((w_sndpkt)); #ifdef SRT_ENABLE_MAXREXMITBW if (payload == 0) { @@ -10209,25 +10202,31 @@ bool CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, CNe // Updates the data that will be next used in packLostData() in next calls. updateSenderMeasurements(payload != 0); + CPacket& w_packet = w_sndpkt.pkt; + IF_HEAVY_LOGGING(const char* reason); // The source of the data packet (normal/rexmit/filter) if (payload > 0) { IF_HEAVY_LOGGING(reason = "reXmit"); } - else if (m_PacketFilter && // XXX m_iSndCurrSeqNo requires locking m_RcvAckLock - m_PacketFilter.packControlPacket(m_iSndCurrSeqNo, m_CryptoControl.getSndCryptoFlags(), (w_packet))) + else if (m_PacketFilter && // XXX m_iSndCurrSeqNo requires locking m_RcvAckLock, although it's only modified in Snd thread + m_PacketFilter.packControlPacket(m_iSndCurrSeqNo, m_CryptoControl.getSndCryptoFlags(), (w_sndpkt.pkt))) { HLOGC(qslog.Debug, log << CONID() << "filter: filter/CTL packet ready - packing instead of data."); - payload = (int) w_packet.getLength(); + payload = (int) w_sndpkt.pkt.getLength(); IF_HEAVY_LOGGING(reason = "filter"); + // just in case - w_sndpkt does not refer to any reserved packet in the sender buffer + w_sndpkt.srcbuf = NULL; + w_sndpkt.seqno = SRT_SEQNO_NONE; + // Stats ScopedLock lg(m_StatsLock); m_stats.sndr.sentFilterExtra.count(1); } else { - if (!packUniqueData(w_packet)) + if (!packUniqueData(w_sndpkt)) { m_tsNextSendTime = steady_clock::time_point(); m_tdSendTimeDiff = steady_clock::duration(); @@ -10338,7 +10337,7 @@ bool CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, CNe return payload >= 0; // XXX shouldn't be > 0 ? == 0 is only when buffer range exceeded. } -bool CUDT::packUniqueData(CPacket& w_packet) +bool CUDT::packUniqueData(CSndPacket& w_sndpkt) { int current_sequence_number; // reflexing variable int kflg; @@ -10365,7 +10364,7 @@ bool CUDT::packUniqueData(CPacket& w_packet) // isn't a useless redundant state copy. If it is, then taking the flags here can be removed. kflg = m_CryptoControl.getSndCryptoFlags(); int pktskipseqno = 0; - pld_size = m_pSndBuffer->readData((w_packet), (tsOrigin), kflg, (pktskipseqno)); + pld_size = m_pSndBuffer->extractUniquePacket((w_sndpkt), (tsOrigin), kflg, (pktskipseqno)); if (pktskipseqno) { // Some packets were skipped due to TTL expiry. @@ -10387,6 +10386,8 @@ bool CUDT::packUniqueData(CPacket& w_packet) current_sequence_number = m_iSndCurrSeqNo; } + CPacket& w_packet = w_sndpkt.pkt; + #if SRT_ENABLE_BONDING // Fortunately the group itself isn't being accessed. if (!m_bClosing && m_parent->m_GroupOf) diff --git a/srtcore/core.h b/srtcore/core.h index 6136b227f..51af46fb5 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1322,17 +1322,17 @@ class CUDT /// Pack a packet from a list of lost packets. /// @param packet [in, out] a packet structure to fill /// @return payload size on success, <=0 on failure - int packLostData(CPacket &packet); + int packLostData(CSndPacket &packet); int32_t getCleanRexmitOffset(); bool checkRexmitRightTime(int32_t seqno, const sync::steady_clock::time_point& current_time); - int extractCleanRexmitPacket(int32_t seqno, CPacket& w_packet, sync::steady_clock::time_point& w_tsOrigin); + int extractCleanRexmitPacket(int32_t seqno, CSndPacket& w_packet, sync::steady_clock::time_point& w_tsOrigin); /// Pack a unique data packet (never sent so far) in CPacket for sending. /// @param packet [in, out] a CPacket structure to fill. /// /// @return true if a packet has been packets; false otherwise. - bool packUniqueData(CPacket& packet); + bool packUniqueData(CSndPacket& packet); /// Pack in CPacket the next data to be send. If the call succeeds, the sequence number /// of the packet is reserved and guaranteed to stay in the buffer. This should be later @@ -1344,7 +1344,7 @@ class CUDT /// /// @retval true A packet was extracted for sending, the socket should be rechecked at @a nexttime /// @retval false Nothing was extracted for sending, @a nexttime should be ignored - bool packData(CPacket& packet, time_point& nexttime, CNetworkInterface& src_addr); + bool packData(CSndPacket& packet, time_point& nexttime, CNetworkInterface& src_addr); bool releaseSend(); /// Also excludes srt::CUDTUnited::m_GlobControlLock. diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 813f71343..f089186ea 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -522,10 +522,10 @@ void CSndQueue::workerSendOrder() } // pack a packet from the socket - CPacket pkt; + CSndPacket sndpkt; steady_clock::time_point next_send_time; CNetworkInterface source_addr; - const bool res = u.packData((pkt), (next_send_time), (source_addr)); + const bool res = u.packData((sndpkt), (next_send_time), (source_addr)); // Check if extracted anything to send if (res == false) @@ -548,9 +548,11 @@ void CSndQueue::workerSendOrder() sched.remove(runner); } - HLOGC(qslog.Debug, log << CONID() << "chn:SENDING: " << pkt.Info()); - m_pChannel->sendto(addr, pkt, source_addr); - u.releaseSend(); + HLOGC(qslog.Debug, log << CONID() << "chn:SENDING: " << sndpkt.pkt.Info()); + m_pChannel->sendto(addr, sndpkt.pkt, source_addr); + // NOTE: Destructor of CSndPacket will release this packet's seqno + // from CSndBuffer and will try to remove packets from this one up to ACK + // if any are still present. } THREAD_EXIT(); diff --git a/test/test_buffer_snd.cpp b/test/test_buffer_snd.cpp index 110354a01..e21548fb5 100644 --- a/test/test_buffer_snd.cpp +++ b/test/test_buffer_snd.cpp @@ -4,6 +4,7 @@ #include #include #include "ofmt.h" +#include "sync.h" using namespace std; @@ -59,18 +60,38 @@ class TestSndBuffer: public testing::Test m_buffer->cancelLostSeq(seq); } - size_t readUnique(CPacket& pkt, int kflg) + size_t readUniqueForget() { int pktskipseqno = 0; + int kflg = 0; time_point tsOrigin; - int size = m_buffer->readData((pkt), (tsOrigin), kflg, (pktskipseqno)); + CSndPacket sndpkt; + int size = m_buffer->extractUniquePacket((sndpkt), (tsOrigin), kflg, (pktskipseqno)); // Ignore intermediate data return size_t(size); } - size_t readOld(int32_t seqno, CPacket& w_pkt, std::list>& w_dropseq) + size_t readUniqueKeep(CSndPacket& sndpkt) + { + int pktskipseqno = 0; + int kflg = 0; + time_point tsOrigin; + + int size = m_buffer->extractUniquePacket((sndpkt), (tsOrigin), kflg, (pktskipseqno)); + // Ignore intermediate data + + return size_t(size); + } + + size_t readOld(int32_t seqno, CSndPacket& w_pkt) + { + std::list> dropseq; + return readOld(seqno, (w_pkt), (dropseq)); + } + + size_t readOld(int32_t seqno, CSndPacket& w_pkt, std::list>& w_dropseq) { for (;;) { @@ -132,15 +153,15 @@ TEST_F(TestSndBuffer, Basic) // Now let's read 3 packets from it CPacket rpkt; - EXPECT_NE(readUnique((rpkt), 0), 0); - EXPECT_NE(readUnique((rpkt), 0), 0); - EXPECT_NE(readUnique((rpkt), 0), 0); + EXPECT_NE(readUniqueForget(), 0); + EXPECT_NE(readUniqueForget(), 0); - // AFTER READING you need to declare you no longer need them. - m_buffer->releaseSeqno(12345); // we haven't ACKed anything yet. + { + CSndPacket spkt; + EXPECT_NE(readUniqueKeep((spkt)), 0); + } // And let's see - sout.puts("AFTER extracting 3 packets:"); sout.puts(m_buffer->show()); @@ -150,6 +171,22 @@ TEST_F(TestSndBuffer, Basic) sout.puts("AFTER scheduling #1 and #2 for rexmit:"); sout.puts(m_buffer->show()); + { + // Now read one packet as old at seq 12346, and while keeping it, ACK up to 12347. + CSndPacket snd; + EXPECT_NE(readOld(12346, (snd)), 0); + revokeSeq(12347); + + // SHOULD ACK only up to 12346. + EXPECT_EQ(m_buffer->firstSeqNo(), 12346); + + sout.puts("READ 12346 and ack up to 12347:"); + sout.puts(m_buffer->show()); + } + + sout.puts("RELEASED send packet 12346:"); + sout.puts(m_buffer->show()); + // Now remove up to the second one revokeSeq(12347); // this is the first seq that should stay @@ -157,12 +194,13 @@ TEST_F(TestSndBuffer, Basic) sout.puts(m_buffer->show()); // Now read 4 more packets - EXPECT_NE(readUnique((rpkt), 0), 0); - EXPECT_NE(readUnique((rpkt), 0), 0); - EXPECT_NE(readUnique((rpkt), 0), 0); - EXPECT_NE(readUnique((rpkt), 0), 0); - - m_buffer->releaseSeqno(12345); // we haven't ACKed anything yet. + EXPECT_NE(readUniqueForget(), 0); + EXPECT_NE(readUniqueForget(), 0); + EXPECT_NE(readUniqueForget(), 0); + { + CSndPacket spkt; + EXPECT_NE(readUniqueKeep(spkt), 0); + } // Then add two rexmit requests scheduleRexmit(12348, 12349); @@ -233,6 +271,124 @@ TEST_F(TestSndBuffer, Basic) sout.puts(m_buffer->show()); } +static size_t generateRandomPayload(char* pw_out, size_t minsize, size_t maxsize) +{ + // Generate the size + size_t size = genRandomInt(minsize, maxsize); + + for (size_t i = 0; i < size; ++i) + pw_out[i] = genRandomInt(32, 127); + + return size; +} + // Second test for sender buffer should use multiple threads for // scheduling packets, scheduling losses, and picking up packets for sending. +TEST_F(TestSndBuffer, Threaded) +{ + // We create 2 threads: + // 1. Sender Thread: will get packets from the buffer and "send" them. + // The thread is controlled by the timer that gives it 0.2s between + // each reading request. We try first to get a loss, and if this isn't + // delivered, a new unique packet. + + auto sender_thread_fn = [this] () + { + for (;;) + { + // XXX try to fuzzy this value a bit + std::this_thread::sleep_for(200ms); + + sout.puts("[S] Checking on LOSS seq"); + + // Check if a lost sequence is available + CSndBuffer::DropRange buffer_drop; + int32_t seq = m_buffer->popLostSeq((buffer_drop)); + if (seq != SRT_SEQNO_NONE) + { + // Pick up the loss and "send" it. + CSndPacket snd; + int payload = readOld(seq, (snd)); + EXPECT_GT(payload, 0); + + // "send" it. + char buf[1024]; + memcpy(buf, snd.pkt.data(), snd.pkt.size()); + sout.puts("[S] Lost packet %", seq, " !", BufferStamp(buf, snd.pkt.size())); + + continue; + } + + CSndPacket snd; + int pld_size = readUniqueKeep((snd)); + if (pld_size == 0) // no more packets + { + sout.puts("[S] NO MORE PACKETS, exitting"); + return; + } + EXPECT_NE(pld_size, -1); + + // "send" it. + char buf[1024]; + memcpy(buf, snd.pkt.data(), snd.pkt.size()); + sout.puts("[S] Unique packet %", snd.seqno, " !", BufferStamp(buf, snd.pkt.size())); + } + }; + + // 2. Update Thread: will simulate ACK or LOSS reception and update the + // sender buffer accordingly. + + auto update_thread_fn = [this] () + { + // This should be already after sending 4 packets. + std::this_thread::sleep_for(1s); + // + // So now declare packet 3 as lost + + int32_t lostseq = 12345 + 3; + sout.puts("[U] Adding loss info: %", lostseq); + scheduleRexmit(lostseq, lostseq); + + std::this_thread::sleep_for(200ms); + // After that you should expect the lost packet retransmitted, + // so fake having received ACK + + sout.puts("[U] ACK %", 12348); + revokeSeq(12349); + + // Just in case + std::this_thread::sleep_for(200ms); + + sout.puts("[U] ACK %", 12355); + revokeSeq(12355); + }; + + std::thread sender_thread (sender_thread_fn); + + std::thread update_thread (update_thread_fn); + + // Ok; main thread is going to submit packets, + // then wait until all other threads are finished. + + // (secondary threads are starting with some slip, so + // we have a guarantee to get at least one packet send-ready) + + // 32 is the total capacity + for (int i = 0; i < 24; ++i) + { + char buf[1024]; + size_t size = generateRandomPayload((buf), 384, 1001); + + sout.puts("[A] Sending payload size=", size, " !", BufferStamp(buf, size)); + + addBuffer(buf, size, i+1); + + std::this_thread::sleep_for(100ms); //2* faster than reading + } + + sout.puts("[A] DONE, waiting for others to finish"); + sender_thread.join(); + update_thread.join(); +} + From 1213952864119d6f862c655d9cb40bdd4b8d5fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Tue, 25 Nov 2025 15:24:38 +0100 Subject: [PATCH 12/26] Fixed build break --- srtcore/core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index c6b166236..6ef713785 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9885,7 +9885,7 @@ int CUDT::packLostData(CSndPacket& w_sndpkt) // Token consumption will only happen when the retransmission // effectively happens. // XXX NOTE: In version 1.6.0 use the IP-version dependent value for UDP_HDR_SIZE - size_t network_size = w_sndpkt.getLength() + CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion); + size_t network_size = w_packet.getLength() + CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion); m_SndRexmitShaper.consumeTokens(network_size); HLOGC(qslog.Debug, log << "REXMIT-SH: consumed " << network_size << " tokens, remain " << m_SndRexmitShaper.ntokens()); } From 97c005f3a728cf87f79fbb2caa8056fee0d7aa0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Wed, 26 Nov 2025 09:39:24 +0100 Subject: [PATCH 13/26] Fixed bug: missing setting srctime when extracting. Fixed bug: missing destructor for CSndBuffer internal container --- srtcore/buffer_snd.cpp | 22 ++++++++++++++++++++++ srtcore/buffer_snd.h | 4 +++- srtcore/core.cpp | 2 ++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 651273584..5357c54fe 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -324,6 +324,7 @@ int CSndBuffer::extractUniquePacket(CSndPacket& w_packet, steady_clock::time_poi p->m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(kflgs); } w_packet.pkt.set_msgflags(p->m_iMsgNoBitset); + w_srctime = p->m_tsOriginTime; // Also make THIS packet busy. ++p->m_iBusy; @@ -779,6 +780,27 @@ size_t SndPktArray::pop(size_t n) return n; } +// Destructor does the same as pop(size()), except that we can´t deny deletion +// for any reason. If m_iBusy found, all we can do is to issue an error log, +// but deletion must still happen otherwise it will be a leak. +SndPktArray::~SndPktArray() +{ + for (deque::iterator i = m_PktQueue.begin(); + i != m_PktQueue.end(); ++i) + { + // Stop at first busy. + if (i->m_iBusy) + { + LOGC(bslog.Fatal, log << "IPE: CSndBuffer.Array packet =" << distance(m_PktQueue.begin(), i) + << " %" << i->m_iSeqNo << " HAS STILL " << i->m_iBusy << " USERS!"); + } + // Deallocate storage + m_Storage.put(i->m_pcData); + } + + m_PktQueue.clear(); +} + bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& next_rexmit_time) { // Can't install loss to an empty container diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index 8fc691ecb..a06c27079 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -238,6 +238,8 @@ struct SndPktArray m_Storage.reserve(max_packets); } + ~SndPktArray(); + int unique_size() const { return m_iNewQueued; } // GET, means the packet is still in the container, you just get access to it. @@ -269,7 +271,7 @@ struct SndPktArray for (int i = ixlo; i <= ixhi; ++i) { // Do not override existing time value, only set anew if 0 - if (is_zero(m_PktQueue[i].m_tsNextRexmitTime)) + if (sync::is_zero(m_PktQueue[i].m_tsNextRexmitTime)) m_PktQueue[i].m_tsNextRexmitTime = time; } } diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 6ef713785..92c213c26 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -10030,6 +10030,8 @@ void CUDT::setDataPacketTS(CPacket& p, const time_point& ts) return; } + SRT_ASSERT(!sync::is_zero(ts)); + // TODO: Might be better for performance to ensure this condition is always false, and just use SRT_ASSERT here. if (ts < tsStart) { From 16d3f20da913150e30cae6b20768fd5864fbd3eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 4 Dec 2025 11:51:12 +0100 Subject: [PATCH 14/26] Rewritten insert_loss. Reshaped the code to collect all SndPktArray methods in one place. Added unit tests for sender loss handling. --- logging/logging.h | 2 +- srtcore/buffer_snd.cpp | 1353 +++++++++++++++++++++++--------------- srtcore/buffer_snd.h | 63 +- srtcore/common.h | 8 - test/test_buffer_snd.cpp | 800 +++++++++++++++++++++- 5 files changed, 1683 insertions(+), 543 deletions(-) diff --git a/logging/logging.h b/logging/logging.h index 649b7d3c1..53378a962 100644 --- a/logging/logging.h +++ b/logging/logging.h @@ -484,7 +484,7 @@ class LogConfig { size_t fa = farray[i]; if (fa < enabled_fa.size()) - enabled_fa[fa] = true; + enabled_fa[fa] = enabled; } } updateLoggersState(); diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 5357c54fe..d8fdde873 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -251,21 +251,6 @@ int CSndBuffer::addBufferFromFile(fstream& ifs, int len) return total; } -SndPktArray::Packet* SndPktArray::extract_unique() -{ - // It should be only possible to be 0, but just in case. - if (m_iNewQueued <= 0) - return NULL; - - SRT_ASSERT(m_iNewQueued <= int(m_PktQueue.size())); - - // If m_iNewQueued == 1, then only the last item is the unique one, - // which's index is size()-1. - size_t index = int(m_PktQueue.size()) - m_iNewQueued; - --m_iNewQueued; // We checked in advance that it's > 0. - return &m_PktQueue[index]; -} - int CSndBuffer::extractUniquePacket(CSndPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc) { int readlen = 0; @@ -551,202 +536,452 @@ bool CSndBuffer::revoke(int32_t seqno) return true; } -// NOTE: 'n' is the index in the m_PktQueue array -// up to which (including) the losses must be cleared off. -// This should only result in making the m_iFirstRexmit -// and m_iLastRexmit field pointing to either -1 or -// valid indexes in the container, but OUTSIDE the range -// from 0 to n. -void SndPktArray::remove_loss(int last_to_clear) +bool CSndBuffer::cancelLostSeq(int32_t seq) { - // This is going to remove the loss records since the first one - // up to the packet designated by the last_to_clear offset (same as pop()). - - // this empty() is just formally - with empty m_iFirstRexmit should be moreover -1 - if (m_iFirstRexmit == -1 || m_PktQueue.empty()) // no loss records - return; // last is also -1 in this situation - - const int LASTX = int(m_PktQueue.size()) - 1; - - // Handle special case: if last_to_clear is the last index in the container, - // simply remove everything. Just update all the loss nodes. - if (last_to_clear >= LASTX) - { - for (int loss = m_iFirstRexmit, next; loss != -1; loss = next) - { - // use safe-loop rule because the node data will be cleared here. - next = next_loss(loss); - SndPktArray::Packet& p = m_PktQueue[loss]; - p.m_iLossLength = 0; - p.m_iNextLossGroupOffset = 0; - } - m_iFirstRexmit = -1; - m_iLastRexmit= -1; - m_iLossLengthCache = 0; - return; - } - - // The iteration rule here: - // Make calculations on the indexes with unchanged relative values. - // That is, just clear the records that point to a less index than last_to_clear. - // Values of m_iFirstRexmit and m_iLastRexmit still refer to the unchanged - // indexes in the container, just must be outside the removed region. - int removed_loss_length = 0; - int first_to_clear = -1; - for (;;) - { - if (last_to_clear < m_iFirstRexmit) - { - // Found at THIS RECORD (possibly having dismissed earlier ones) - // that it's already in the non-revoked region. - - // Update with the length of every loss record removed in this loop. - m_iLossLengthCache = m_iLossLengthCache - removed_loss_length; - // That's it, nothing more to do. - break; - } - - if (first_to_clear == -1) - first_to_clear = m_iFirstRexmit; - - // Ride until you find a split-in-half record, - // a new record beyond last_to_clear, or no more records. - SndPktArray::Packet& p = m_PktQueue[m_iFirstRexmit]; - - int last_index = m_iFirstRexmit + p.m_iLossLength - 1; - if (last_to_clear < last_index) - { - // split-in-half case. This will be the last on which the operation is done. + ScopedLock bufferguard(m_BufLock); - int new_beginning = last_to_clear + 1; // The case when last_to_clear == m_PktQueue.size() - 1 is handled already + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); + if (offset < 0 || offset >= int(m_Packets.size())) + return false; - int revoked_length_fragment = new_beginning - m_iFirstRexmit; + return m_Packets.clear_loss(offset); +} - // Now shift the position - bool is_last = false; - m_PktQueue[new_beginning].m_iLossLength = p.m_iLossLength - revoked_length_fragment; - if (p.m_iNextLossGroupOffset) - { - int next_index = m_iFirstRexmit + p.m_iNextLossGroupOffset; - // Replicate the distance at the new index - m_PktQueue[new_beginning].m_iNextLossGroupOffset = next_index - new_beginning; - } - else - { - // No next group, this is the last one. - m_PktQueue[new_beginning].m_iNextLossGroupOffset = 0; - is_last = true; - } +int32_t CSndBuffer::popLostSeq(DropRange& w_drop) +{ + static const DropRange nodrop = { {SRT_SEQNO_NONE, SRT_SEQNO_NONE}, SRT_MSGNO_CONTROL }; + w_drop = nodrop; - // Cancel the previous first node - p.m_iLossLength = 0; - p.m_iNextLossGroupOffset = 0; + ScopedLock bufferguard(m_BufLock); - // NOTE: the new values of m_iFirstRexmit and m_iLastRexmit set here - // are valid indexes AFTER REMOVAL of the revoked elements from m_PktQueue. - m_iFirstRexmit = new_beginning; - if (is_last) - m_iLastRexmit = m_iFirstRexmit; - // If not last, there is some record next to first which remains last. + // In this version we don't predict any drop requests; + // the sequence is taken directly from the sender buffer, so there's + // physically no possibility to have any sequence lost that is not + // among the packets in the buffer. - // Removed were all previous completely skipped record before length last_to_clear, - // plus a fragment of the record that was split in half. - m_iLossLengthCache = m_iLossLengthCache - removed_loss_length - revoked_length_fragment; + // Ok, this is our sequence to report. + int i = m_Packets.extractFirstLoss(); + if (i == -1) + return SRT_SEQNO_NONE; - break; - } + int32_t seqno = CSeqNo::incseq(m_iSndLastDataAck, i); + return seqno; +} - // Check if this one was the last record; if so, we have cleared all. - if (p.m_iNextLossGroupOffset == 0) - { - p.m_iLossLength = 0; - m_iFirstRexmit = -1; - m_iLastRexmit = -1; - m_iLossLengthCache = 0; - break; - } +void CSndBuffer::removeLossUpTo(int32_t seqno) +{ + ScopedLock bufferguard(m_BufLock); + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); - // Remaining case: the whole record is below last_to_clear (so remove it and try next) - // Remove means that you need to clear this packet from being a hook of - // a loss record, and move m_iFirstRexmit to the next record. - removed_loss_length += p.m_iLossLength; - m_iFirstRexmit += p.m_iNextLossGroupOffset; + if (offset < 0 || offset >= int(m_Packets.size())) + return; - p.m_iLossLength = 0; - p.m_iNextLossGroupOffset = 0; - } + m_Packets.remove_loss(offset); } -bool CSndBuffer::cancelLostSeq(int32_t seq) +int CSndBuffer::insertLoss(int32_t seqlo, int32_t seqhi, const time_point& pt) { ScopedLock bufferguard(m_BufLock); + int offset_lo = CSeqNo::seqoff(m_iSndLastDataAck, seqlo); + int offset_hi = CSeqNo::seqoff(m_iSndLastDataAck, seqhi); - int offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); - if (offset < 0 || offset >= int(m_Packets.size())) - return false; + return m_Packets.insert_loss(offset_lo, offset_hi, is_zero(pt) ? steady_clock::now(): pt); +} - return m_Packets.clear_loss(offset); +int CSndBuffer::getLossLength() +{ + return m_Packets.loss_length(); } -bool SndPktArray::clear_loss(int index) +int CSndBuffer::getCurrBufSize() const { - // Just access the record. Now return false only - // if it turns out that it has never been rexmit-scheduled. - SndPktArray::Packet& p = m_PktQueue[index]; - if (is_zero(p.m_tsNextRexmitTime)) - return false; + return m_Packets.size(); +} - p.m_tsNextRexmitTime = time_point(); - return true; +int CSndBuffer::getMaxPacketLen() const +{ + return m_iBlockLen - m_iReservedSize; } -void SndPktArray::clear() +int CSndBuffer::countNumPacketsRequired(int iPldLen) const { - // pop() will do erase(begin(), end()) in this case, - // but will also destroy every packet. - pop(size()); + const int iPktLen = getMaxPacketLen(); + return number_slices(iPldLen, iPktLen); } -SndPktArray::Packet& SndPktArray::push() +int CSndBuffer::getAvgBufSize(int& w_bytes, int& w_tsp) { - m_PktQueue.push_back(Packet()); - m_iCachedSize = m_PktQueue.size(); + ScopedLock bufferguard(m_BufLock); /* Consistency of pkts vs. bytes vs. spantime */ - Packet& that = m_PktQueue.back(); + /* update stats in case there was no add/ack activity lately */ + updAvgBufSize(steady_clock::now()); - // Allocate the packet payload space - that.m_iBusy = 0; - that.m_iLength = m_Storage.blocksize; - that.m_pcData = m_Storage.get(); + // Average number of packets and timespan could be small, + // so rounding is beneficial, while for the number of + // bytes in the buffer is a higher value, so rounding can be omitted, + // but probably better to round all three values. + w_bytes = m_mavg.bytes() + 0.49; + w_tsp = m_mavg.timespan_ms() + 0.49; + return int(m_mavg.pkts() + 0.49); +} - // pushing is always treated as adding a new unique packet - ++m_iNewQueued; +void CSndBuffer::updAvgBufSize(const steady_clock::time_point& now) +{ + if (!m_mavg.isTimeToUpdate(now)) + return; - // Return as is - without initialized fields. - return that; + int bytes = 0; + int timespan_ms = 0; + const int pkts = getBufferStats((bytes), (timespan_ms)); + m_mavg.update(now, pkts, bytes, timespan_ms); } -// 'n' is the past-the-end index for removal -size_t SndPktArray::pop(size_t n) +int CSndBuffer::getBufferStats(int& w_bytes, int& w_timespan) const { - if (m_PktQueue.empty() || !n) - return 0; // The size is also unchanged + w_bytes = m_iBytesCount; + /* + * Timespan can be less then 1000 us (1 ms) if few packets. + * Also, if there is only one pkt in buffer, the time difference will be 0. + * Therefore, always add 1 ms if not empty. + */ + if (m_Packets.empty()) + w_timespan = 0; + else + w_timespan = count_milliseconds(m_tsLastOriginTime - m_Packets[0].m_tsOriginTime) + 1; - if (n > m_PktQueue.size()) - n = m_PktQueue.size(); - // We consider that this call clears off losses in the container - // calls from 0 to n (inc). + return m_Packets.size(); +} - // NOTE: Losses are removed anyway, regardless of the busy status. - remove_loss(n-1); // remove_loss includes given index +CSndBuffer::duration CSndBuffer::getBufferingDelay(const time_point& tnow) const +{ + ScopedLock lck(m_BufLock); - deque::iterator i = m_PktQueue.begin(), upto = i + n; - for (; i != upto; ++i) - { - // Stop at first busy. - if (i->m_iBusy) - { + if (m_Packets.empty()) + return duration(0); + + return tnow - m_Packets[0].m_tsOriginTime; +} + +int CSndBuffer::dropLateData(int& w_bytes, int32_t& w_first_msgno, const steady_clock::time_point& too_late_time) +{ + ScopedLock bufferguard(m_BufLock); + + int dbytes = 0; + int32_t msgno = 0; + // Reach out to the position that is less than too_late_time, + // counting the bytes + size_t i; + for (i = 0; i < m_Packets.size(); ++i) + { + SndPktArray::Packet& p = m_Packets[i]; + // Stop on first busy or young enough + if (p.m_iBusy || p.m_tsOriginTime >= too_late_time) + break; + dbytes += p.m_iLength; + msgno = p.getMsgSeq(); + } + + // Now delete all these packets from the container + if (i) + { + // As the loop stopped on first busy, we ignore the return value + // because there should be no busy packets in this range. + m_Packets.pop(i); + + const int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, i); + m_iSndLastDataAck = fakeack; + } + + w_bytes = dbytes; // even if 0 + + // We report the increased number towards the last ever seen + // by the loop, as this last one is the last received. So remained + // (even if "should remain") is the first after the last removed one. + w_first_msgno = ++MsgNo(msgno); + + updAvgBufSize(steady_clock::now()); + + return i; +} + +int CSndBuffer::dropAll(int& w_bytes) +{ + ScopedLock bufferguard(m_BufLock); + // clear all + + int dpkts = m_Packets.size(); + m_Packets.clear(); + w_bytes = m_iBytesCount; + m_iBytesCount = 0; + + updAvgBufSize(steady_clock::now()); + return dpkts; +} + +CSndBuffer::~CSndBuffer() +{ + releaseMutex(m_BufLock); +} + +string CSndBuffer::show() const +{ + using namespace hvu; + ofmtbufstream out; + + int minw = 2; + if (m_Packets.size() > 99) + minw = 3; + else if (m_Packets.size() > 999) + minw = 4; + + fmtc findex = fmtc().width(minw).fillzero(); + + ScopedLock bufferguard(m_BufLock); + + SndPktArray::PacketShowState st; + for (size_t i = 0; i < m_Packets.size(); ++i) + { + int seqno = CSeqNo::incseq(m_iSndLastDataAck, i); + out << "[" << fmt(i, findex) << "]%" << seqno << ": "; + m_Packets.showline(i, (st), (out)); + out.puts(); + } + + return out.str(); +} + + +// SndPktArray implementation + +SndPktArray::Packet* SndPktArray::extract_unique() +{ + // It should be only possible to be 0, but just in case. + if (m_iNewQueued <= 0) + return NULL; + + SRT_ASSERT(m_iNewQueued <= int(m_PktQueue.size())); + + // If m_iNewQueued == 1, then only the last item is the unique one, + // which's index is size()-1. + size_t index = int(m_PktQueue.size()) - m_iNewQueued; + --m_iNewQueued; // We checked in advance that it's > 0. + return &m_PktQueue[index]; +} + +int SndPktArray::next_loss(int current_loss) +{ + if (current_loss == -1) + return -1; + + SRT_ASSERT(current_loss < int(m_PktQueue.size())); + + Packet& p = m_PktQueue[current_loss]; + SRT_ASSERT(p.m_iLossLength > 0); + + if (p.m_iNextLossGroupOffset == 0) + return -1; // The last loss + + SRT_ASSERT(p.m_iLossLength < p.m_iNextLossGroupOffset); + + SRT_ASSERT(current_loss + p.m_iNextLossGroupOffset < int(m_PktQueue.size())); + + return current_loss + p.m_iNextLossGroupOffset; +} + +// NOTE: 'n' is the index in the m_PktQueue array +// up to which (including) the losses must be cleared off. +// This should only result in making the m_iFirstRexmit +// and m_iLastRexmit field pointing to either -1 or +// valid indexes in the container, but OUTSIDE the range +// from 0 to n. +void SndPktArray::remove_loss(int last_to_clear) +{ + // This is going to remove the loss records since the first one + // up to the packet designated by the last_to_clear offset (same as pop()). + + // this empty() is just formally - with empty m_iFirstRexmit should be moreover -1 + if (m_iFirstRexmit == -1 || m_PktQueue.empty()) // no loss records + return; // last is also -1 in this situation + + const int LASTX = int(m_PktQueue.size()) - 1; + + // Handle special case: if last_to_clear is the last index in the container, + // simply remove everything. Just update all the loss nodes. + if (last_to_clear >= LASTX) + { + for (int loss = m_iFirstRexmit, next; loss != -1; loss = next) + { + // use safe-loop rule because the node data will be cleared here. + next = next_loss(loss); + SndPktArray::Packet& p = m_PktQueue[loss]; + p.m_iLossLength = 0; + p.m_iNextLossGroupOffset = 0; + } + m_iFirstRexmit = -1; + m_iLastRexmit= -1; + m_iLossLengthCache = 0; + string msg SRT_ATR_UNUSED; + SRT_ASSERT(validateLossIntegrity((msg))); + return; + } + + // The iteration rule here: + // Make calculations on the indexes with unchanged relative values. + // That is, just clear the records that point to a less index than last_to_clear. + // Values of m_iFirstRexmit and m_iLastRexmit still refer to the unchanged + // indexes in the container, just must be outside the removed region. + int removed_loss_length = 0; + int first_to_clear = -1; + for (;;) + { + if (last_to_clear < m_iFirstRexmit) + { + // Found at THIS RECORD (possibly having dismissed earlier ones) + // that it's already in the non-revoked region. + + // Update with the length of every loss record removed in this loop. + m_iLossLengthCache = m_iLossLengthCache - removed_loss_length; + // That's it, nothing more to do. + break; + } + + if (first_to_clear == -1) + first_to_clear = m_iFirstRexmit; + + // Ride until you find a split-in-half record, + // a new record beyond last_to_clear, or no more records. + SndPktArray::Packet& p = m_PktQueue[m_iFirstRexmit]; + + int last_index = m_iFirstRexmit + p.m_iLossLength - 1; + if (last_to_clear < last_index) + { + // split-in-half case. This will be the last on which the operation is done. + + int new_beginning = last_to_clear + 1; // The case when last_to_clear == m_PktQueue.size() - 1 is handled already + SRT_ASSERT(new_beginning > m_iFirstRexmit); + SRT_ASSERT(new_beginning < int(m_PktQueue.size())); + + int revoked_length_fragment = new_beginning - m_iFirstRexmit; + + // Now shift the position + bool is_last = false; + m_PktQueue[new_beginning].m_iLossLength = p.m_iLossLength - revoked_length_fragment; + if (p.m_iNextLossGroupOffset) + { + int next_index = m_iFirstRexmit + p.m_iNextLossGroupOffset; + // Replicate the distance at the new index + m_PktQueue[new_beginning].m_iNextLossGroupOffset = next_index - new_beginning; + SRT_ASSERT(new_beginning + m_PktQueue[new_beginning].m_iNextLossGroupOffset < int(m_PktQueue.size())); + } + else + { + // No next group, this is the last one. + m_PktQueue[new_beginning].m_iNextLossGroupOffset = 0; + is_last = true; + } + + // Cancel the previous first node + p.m_iLossLength = 0; + p.m_iNextLossGroupOffset = 0; + + // NOTE: the new values of m_iFirstRexmit and m_iLastRexmit set here + // are valid indexes AFTER REMOVAL of the revoked elements from m_PktQueue. + m_iFirstRexmit = new_beginning; + if (is_last) + m_iLastRexmit = m_iFirstRexmit; + // If not last, there is some record next to first which remains last. + + // Removed were all previous completely skipped record before length last_to_clear, + // plus a fragment of the record that was split in half. + m_iLossLengthCache = m_iLossLengthCache - removed_loss_length - revoked_length_fragment; + + break; + } + + // Check if this one was the last record; if so, we have cleared all. + if (p.m_iNextLossGroupOffset == 0) + { + p.m_iLossLength = 0; + m_iFirstRexmit = -1; + m_iLastRexmit = -1; + m_iLossLengthCache = 0; + break; + } + + // Remaining case: the whole record is below last_to_clear (so remove it and try next) + // Remove means that you need to clear this packet from being a hook of + // a loss record, and move m_iFirstRexmit to the next record. + removed_loss_length += p.m_iLossLength; + m_iFirstRexmit += p.m_iNextLossGroupOffset; + + p.m_iLossLength = 0; + p.m_iNextLossGroupOffset = 0; + } + + string msg SRT_ATR_UNUSED; + SRT_ASSERT(validateLossIntegrity((msg))); +} + +bool SndPktArray::clear_loss(int index) +{ + // Just access the record. Now return false only + // if it turns out that it has never been rexmit-scheduled. + SndPktArray::Packet& p = m_PktQueue[index]; + if (is_zero(p.m_tsNextRexmitTime)) + return false; + + p.m_tsNextRexmitTime = time_point(); + return true; +} + +void SndPktArray::clear() +{ + // pop() will do erase(begin(), end()) in this case, + // but will also destroy every packet. + pop(size()); +} + +SndPktArray::Packet& SndPktArray::push() +{ + m_PktQueue.push_back(Packet()); + m_iCachedSize = m_PktQueue.size(); + + Packet& that = m_PktQueue.back(); + + // Allocate the packet payload space + that.m_iBusy = 0; + that.m_iLength = m_Storage.blocksize; + that.m_pcData = m_Storage.get(); + + // pushing is always treated as adding a new unique packet + ++m_iNewQueued; + + // Return as is - without initialized fields. + return that; +} + +// 'n' is the past-the-end index for removal +size_t SndPktArray::pop(size_t n) +{ + if (m_PktQueue.empty() || !n) + return 0; // The size is also unchanged + + if (n > m_PktQueue.size()) + n = m_PktQueue.size(); + + // We consider that this call clears off losses in the container + // calls from 0 to n (inc). + + // NOTE: Losses are removed anyway, regardless of the busy status. + remove_loss(n-1); // remove_loss includes given index + + deque::iterator i = m_PktQueue.begin(), upto = i + n; + for (; i != upto; ++i) + { + // Stop at first busy. + if (i->m_iBusy) + { //prematurely interrupted; update n. upto = i; n = std::distance(m_PktQueue.begin(), upto); @@ -805,7 +1040,10 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne { // Can't install loss to an empty container if (m_PktQueue.empty()) + { + HLOGC(bslog.Debug, log << "insert_loss: no packets, no loss inserted"); return false; + } // Fix the indexes if they are out of bound. Note that they can potentially // be very far from the point, but any rollovers should be ignored (there's @@ -813,7 +1051,11 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne // and whether at least a fragment of the range is in the buffer. if (offset_lo > offset_hi || offset_hi < 0 || offset_lo >= int(m_PktQueue.size())) + { + HLOGC(bslog.Debug, log << "insert_loss: invalid offset range " << offset_lo << "..." << offset_hi + << " with size=" << m_PktQueue.size()); return false; + } if (offset_lo < 0) { @@ -830,6 +1072,8 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne //seqhi = CSeqNo::decseq(seqhi, fix); } + HLOGC(bslog.Debug, log << "insert_loss: INSERTING offset " << offset_lo << "..." << offset_hi); + int loss_length = offset_hi - offset_lo + 1; // Ok, check now where the position is towards the @@ -846,220 +1090,448 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne m_iLossLengthCache = loss_length; set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + + HLOGC(bslog.Debug, log << "insert_loss: 1&1 record: " + << offset_lo << "..." << offset_hi << " (" << loss_length << " cells)"); + + SRT_ASSERT(offset_lo + loss_length <= int(m_PktQueue.size())); + + string msg SRT_ATR_UNUSED; + SRT_ASSERT(validateLossIntegrity((msg))); + return true; + } + + // Now we have at least one element, so we treat this now as + // a general case, where we need to find the following: + // - ranges that are before offset_hi + // - ranges that are after offset_lo + // - if no such ranges on any side, set up new_first and new_last + // All other ranges are "joint"; all need to be removed + // and a new node should be defined. + + // NOTE: all local variables from here on must have suffix: + // - _index : position of a meaningful element + // - _shift : relative offset between two indexes + // - _end : the past-the-end INDEX value (an element following + // the last element in the range) + + int last_node_end = getEndIndex(m_iLastRexmit); + int offset_end = offset_hi + 1; + + // Step 1: determine the surrounding ranges. + int before_node_index = -1,// last node disjoint before the current one + after_node_index = -1, // first node disjoint after the current one + lowest_inserted_index = offset_lo, + highest_inserted_index = offset_hi; + + vector removed_node_indexes; + + // 1a. Disjoint preceding/succeeding ranges + + bool outside_disjoint = false, outside_disjoint_front = false; + + if (offset_lo < m_iFirstRexmit) + { + // if offset_end == m_iFirstRexmit, they are glued together! + if (offset_end < m_iFirstRexmit) + { + // We have the very first node. So, all nodes are disjoint after. + after_node_index = m_iFirstRexmit; + outside_disjoint = true; + outside_disjoint_front = true; + } + } + else if (offset_hi > last_node_end) + { + if (offset_lo > last_node_end) + { + before_node_index = m_iLastRexmit; + outside_disjoint = true; + } + } + + // Ok, handle the outside disjoint case now; there's no need + // to do any looping in this case, just hook up the nodes. + if (outside_disjoint) + { + int extra_length; + if (outside_disjoint_front) + { + int previous_first_index = m_iFirstRexmit; + m_iFirstRexmit = offset_lo; + extra_length = setupNode(offset_lo, offset_hi, previous_first_index); + HLOGC(bslog.Debug, log << "insert_loss: DISJOINT front: [INSERTED] | " << previous_first_index); + } + else // outside disjoint back + { + int previous_last_index = m_iLastRexmit; + m_iLastRexmit = offset_lo; + extra_length = offset_hi - offset_lo + 1; + + HLOGC(bslog.Debug, log << "insert_loss: DISJOINT back: " << previous_last_index << "..." + << (getEndIndex(previous_last_index)-1) << " | [INSERTED]"); + + // Length remains unchanged; just pin in the new last one. + m_PktQueue[previous_last_index].m_iNextLossGroupOffset = offset_lo - previous_last_index; + setupNode(offset_lo, offset_hi); + } + + set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + string msg SRT_ATR_UNUSED; + SRT_ASSERT(validateLossIntegrity((msg))); + + m_iLossLengthCache = m_iLossLengthCache + extra_length; + return true; + } + + // Now we need to walk through the elements to separate cases: + // - PREDECESSOR: node with end < offset_lo + // - SUCCESSOR: node with first > offset_hi + 1 (offset_end) + // - OVERLAPPING: nodes (possibly series) that satisfy none of the above. + // While searching for overlapping you may find, after cutting off + // all PREDECESSOR, that the next node is a SUCCESSOR. If this is found, + // it's a MIDDLE-DISJOINT and should be handled in place. + + // The loop doesn't have the same body in both cases, so use a disjoint loop. + + // Immutables: + // Inserted range is overlapping or sticking to any of the existing ranges. + // NOTE: embraces cases when: + // - offset_hi == 2, m_iFirstRexmit == 3 (adjacent) + // - offset_hi == 0, m_iFirstRexmit == 0 (then it will be 0 >= -1) + SRT_ASSERT(offset_hi >= m_iFirstRexmit - 1 && offset_lo <= last_node_end); + + int iloss = m_iFirstRexmit, iloss_end; + + // Collect all nodes that precede the inserted one first. + for (; iloss != -1; iloss = next_loss(iloss)) + { + iloss_end = getEndIndex(iloss); + + // [iloss ... ] iloss_end) | offset_lo ... + if (iloss_end < offset_lo) + { + // PREDECESSOR. + // Continue, but rewrite that as last found such record + before_node_index = iloss; + // NOTE: this node stays as is. + } + // [offset_lo ... offset_hi] | [iloss ... iloss_end) + else if (iloss > offset_end) + { + // MIDDLE-DISJOINT. + // HANDLE IT HERE, as this is also a simple insertion. + // - before_node_index: the node to which this is inserted as next. + // - new_next_index: the node inserted to this a next + int new_next_index = iloss; + int added_length = setupNode(offset_lo, offset_hi, new_next_index); + + // Should be not possible because a case when m_iFirstRexmit > offset_hi + // is already handled as "very first" (one of outside_disjoint). + SRT_ASSERT(before_node_index != -1); + + HLOGC(bslog.Debug, log << "insert_loss: DISJOINT middle: ..." + << (getEndIndex(before_node_index)-1) << " | [INSERTED] | " << new_next_index); + + m_PktQueue[before_node_index].m_iNextLossGroupOffset = offset_lo - before_node_index; + + set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + string msg SRT_ATR_UNUSED; + SRT_ASSERT(validateLossIntegrity((msg))); + + m_iLossLengthCache = m_iLossLengthCache + added_length; + return true; + } + // [iloss ... | offset_lo ... offset_hi | iloss_end) + // or + // [iloss ... | offset_lo | iloss_end) ... offset_hi + // or + // offset_lo ... | [iloss ... iloss_end] ... offset_hi + // + // We don't care so far where offset_hi is towards the rest of the ranges. + // That's about to be seen in the next loop continuation. + else + { + // By elimination, this is OVERLAPPING. + // Stop here and note the earliest index + lowest_inserted_index = std::min(iloss, offset_lo); + break; + } + } + + // One special case can be handled here: if the newly inserted range + // is completely covered by the node pointed by iloss. + if (offset_lo >= iloss && offset_end <= iloss_end) + { + HLOGC(bslog.Debug, log << "insert_loss: SWALLOW: " << iloss << "..." << (iloss_end-1)); + // Just update the time for the requested range, but do nothing else. + // The inserted records completely overlap with the existing ones, + // so no changes are necessary, except updating the retransmission time. + set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); return true; } - // Check relationship with the last one. If past the last one, - // simply pin in the new one. - if (offset_lo >= m_iLastRexmit) - { - // This means, it's eaither overlapping with m_iLastRexmit, - // or past the last record. What we know for sure is that it - // doesn't overlap with any earlier loss records. + // Here we have hit the first node OVERLAPPING with the inserted one, + // possibly one of the series. Continue looping to find the first + // following disjoint, if any. + for (; iloss != -1; iloss = next_loss(iloss)) + { + if (iloss > offset_end) + { + // This may never happen, if there's no following disjoint. + // This will not happen in the first iteration (handled already). + after_node_index = iloss; + break; + } + removed_node_indexes.push_back(iloss); + + iloss_end = getEndIndex(iloss); + highest_inserted_index = std::max(offset_end, iloss_end) - 1; + } + + // Current situation: + // + // [predecessors...; before_node_index...end] | + // [lowest_inserted_index ... highest_inserted_index] | + // [after_node_index...end; successors...] + // If no predecessors, before_node_index == -1. + // If no successors, after_node_index == -1. + + // 1. remove all nodes qualified as overlapping (even if the first + // one begins the newly inserted range). + int removed_length = 0; + IF_HEAVY_LOGGING(int removed_nodes_n = removed_node_indexes.size()); + for (size_t i = 0; i < removed_node_indexes.size(); ++i) + { + int x = removed_node_indexes[i]; + removed_length += m_PktQueue[x].m_iLossLength; + m_PktQueue[x].m_iLossLength = 0; + m_PktQueue[x].m_iNextLossGroupOffset = 0; + } + + // 2. Insert a new node at `lowest_inserted_index` length up to `highest_inserted_index` + int inserted_length = highest_inserted_index - lowest_inserted_index + 1; + + // We do not insert empty ranges anyway. + SRT_ASSERT(inserted_length > 0); + + // Could be false, unless we have handled the "swallow" case already. + SRT_ASSERT(inserted_length > removed_length); + + HLOGC(bslog.Debug, log << "insert_loss: REPLACED " << removed_nodes_n << " nodes with new " + << lowest_inserted_index << "..." << highest_inserted_index << " FOLLOWS:" << after_node_index + << " PRECEDES: " << before_node_index << "..." << (getEndIndex(before_node_index)-1)); + + m_PktQueue[lowest_inserted_index].m_iLossLength = inserted_length; + + // 6. if `after_node_index`, set it as next to this - otherwise set 0 next + if (after_node_index != -1) + { + m_PktQueue[lowest_inserted_index].m_iNextLossGroupOffset = after_node_index - lowest_inserted_index; + } + else + { + m_PktQueue[lowest_inserted_index].m_iNextLossGroupOffset = 0; + // This one is the very last then. + m_iLastRexmit = lowest_inserted_index; + } + + // 5. if `before_node_index`, set this one as next to it. + if (before_node_index != -1) + { + m_PktQueue[before_node_index].m_iNextLossGroupOffset = lowest_inserted_index - before_node_index; + } + else + { + m_iFirstRexmit = lowest_inserted_index; + } + + // Update the length + m_iLossLengthCache = m_iLossLengthCache + inserted_length - removed_length; + + // Set the rexmit time only to the range that was requested to be inserted, + // even if this is effectively a fragment of a record. + set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + string msg SRT_ATR_UNUSED; + SRT_ASSERT(validateLossIntegrity((msg))); + return true; +} + +bool SndPktArray::validateLossIntegrity(std::string& w_message) +{ + if (m_iFirstRexmit == -1) + { + w_message = "Only first empty"; + return m_iLastRexmit == -1; + } + + // Now First is not -1, last must be this one or later + if (m_iLastRexmit == -1) + { + w_message = "Only last empty"; + return false; + } + + // Ok, both have values, check relationship + if (m_iFirstRexmit > m_iLastRexmit) + { + w_message = "FIRST > LAST inconsistency!"; + return false; + } + + // Now trace the whole buffer if elements are consistent: + // - all elements that are not loss nodes, must have len & next = 0 + // - nodes that mark loss area, must have their data > 0, or last should be == 0 + + // First, take the easiest part if there's only one loss. + bool result = true; + if (m_iFirstRexmit == m_iLastRexmit) + { + for (size_t i = 0; i < m_PktQueue.size(); ++i) + { + SndPktArray::Packet& p = m_PktQueue[i]; + if (int(i) == m_iFirstRexmit) + { + // For this, check if the lenght is > 0 and if + // if fits in the container, also next must be 0. + if (p.m_iNextLossGroupOffset != 0 + || p.m_iLossLength < 1 + || (p.m_iLossLength + i) > m_PktQueue.size()) + { + w_message += "WRONG DATA at (the only) loss position; "; + result = false; + } + // But still check the others + } + else + { + // If this isn't the node marking element, all must be 0. + if (p.m_iNextLossGroupOffset != 0 + || p.m_iLossLength != 0) + { + w_message += hvu::fmtcat("Non-node element ", i, " has wrong data; "); + } + } + } + return result; + } + + // Now trace everything since the beginning, using states. + PacketShowState st; - SndPktArray::Packet& butlast = m_PktQueue[m_iLastRexmit]; + hvu::ofmtbufstream os; - // Still, the record can overlap. - int last_end_ix = m_iLastRexmit + butlast.m_iLossLength; // past-the-end! + int last_node = m_iFirstRexmit; + for (size_t i = 0; i < m_PktQueue.size(); ++i) + { + SndPktArray::Packet& p = m_PktQueue[i]; - // If the next to insert is exactly next to the last record, - // only extend the existing last record - if (last_end_ix >= offset_lo) + if (st.next_loss_begin == -1) { - // Overlap. Update the loss length, all else remains untouched. - // old_length defines the fragment that will be wiped due to being - // merged with the current one. - int old_length = butlast.m_iLossLength; - int new_length = offset_hi - m_iLastRexmit + 1; - butlast.m_iLossLength = new_length; - - m_iLossLengthCache = m_iLossLengthCache + new_length - old_length; - set_rexmit_time(m_iLastRexmit, offset_hi, next_rexmit_time); - return true; - } + // Before any loss report yet. + if (int(i) == m_iFirstRexmit) + { + // Hit the first one. Check and record. + // First record must have next because we have handled the single case already + if (p.m_iLossLength < 1 || p.m_iNextLossGroupOffset < 2) + { + os << "FIRST@multiple hit wrong data: len=" << p.m_iLossLength + << " off=" << p.m_iNextLossGroupOffset << " ; "; + result = false; + } - // No overlaps, just add the new last one. - SndPktArray::Packet& last = m_PktQueue[offset_lo]; + // Check if exceeds container + int remaining_length = int(m_PktQueue.size()) - i; + int next_index = i + p.m_iNextLossGroupOffset; - int butlast_distance = offset_lo - m_iLastRexmit; + if (next_index >= int(m_PktQueue.size()) || p.m_iLossLength > remaining_length) + { + os << "FIRST@multiple: wrong offset data; "; + result = false; + } - // The length of the last record remains unchanged, - // just the next-pointer needs to be set. - butlast.m_iNextLossGroupOffset = butlast_distance; + // Still, we caught the first record, so update + // the state. + st.next_loss_begin = next_index; + st.remain_loss_group = p.m_iLossLength; // it includes [i] ! + continue; + } - int new_length = offset_hi - offset_lo + 1; - m_iLastRexmit = offset_lo; - last.m_iNextLossGroupOffset = 0; // this is now the last one - last.m_iLossLength = new_length; - m_iLossLengthCache = m_iLossLengthCache + new_length; - set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); - return true; - } + // No data with no previous record yet. + if (p.m_iLossLength != 0 || p.m_iNextLossGroupOffset != 0) + { + os << "WRONG DATA on index_to_clear; + // Check if the next record was hit. + if (int(i) == st.next_loss_begin) + { + // Can be the last one, but must have at leats 1 length + if (p.m_iLossLength < 1 || p.m_iNextLossGroupOffset < 0) + { + os << "WRONG DATA at #" << i << " found as next loss; "; + result = false; + } - // Current form: - // [FIRST...END] [FURTHER1...END] [FURTHER2...END]... [LAST...END] - // | | \ | - // \ \ [B] - // [A] [C] - // Cases: - // [A] Very first (preceding the first record and not overlapping with it) - // [B] intermixed or overlapped with existing records - // Then the mix of two conditions: - // a.: - // 1. Intermixed with existing - // 2. Overlapping with existing + int remaining_length = m_PktQueue.size() - i; + int next_index = i + p.m_iNextLossGroupOffset; - // This determines the index of the first record that we will - // consider as intermixing or overlapping. + if (next_index >= int(m_PktQueue.size()) || p.m_iLossLength > remaining_length) + { + os << "AT #" << i << ": wrong offset data; "; + result = false; + } - // --- int loss_index_end = m_iFirstRexmit + p.m_iLossLength; - // --- SRT_ASSERT(loss_index_end > m_iFirstRexmit); + if (st.remain_loss_group) + { + os << "AT #" << i << ": still expected " << st.remain_loss_group << " loss packets; "; + result = false; + } - int lowest_lo = m_iFirstRexmit; - int last_preceding_index = -1; + last_node = i; - if (offset_lo < m_iFirstRexmit) - { - // Detemine and handle case A. This doesn't require any modification - // of existing records, just inserting one as the first, and the sofar - // first will be its next. - if (offset_hi < m_iFirstRexmit) - { - SndPktArray::Packet& first = m_PktQueue[offset_lo]; - first.m_iLossLength = loss_length; - first.m_iNextLossGroupOffset = m_iFirstRexmit - offset_lo; - m_iFirstRexmit = offset_lo; - m_iLossLengthCache = m_iLossLengthCache + loss_length; - set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); - return true; + // Still, we caught the first record, so update + // the state. + st.next_loss_begin = i + p.m_iNextLossGroupOffset; + st.remain_loss_group = p.m_iLossLength; // it includes [i] ! + continue; } - // Otherwise continue with first potentially overlapping - // or preceding. - } - // We use else because if the lowest index precedes the first, - // there couldn't exist a case that any whole records precede it. - else - { - // As the lower range is already past the begin, - // skip all records that whole precede the inserted record. - for (int loss_index = m_iFirstRexmit; loss_index != -1; loss_index = next_loss(loss_index)) - { - SndPktArray::Packet& ip = m_PktQueue[loss_index]; - int loss_end = loss_index + ip.m_iLossLength; - if (loss_end >= offset_lo) - { - lowest_lo = loss_index; - break; - } + if (st.remain_loss_group) + { + // update the current one + --st.remain_loss_group; - // If this value remains -1, it means that the record to - // insert overlaps with the very first record. Otherwise - // there will always be some records preceding and this - // needs to be linked with the prospective next one. - last_preceding_index = loss_index; + // If the counter has reached 0, this is the + // first cell past the loss record, but it is + // still a separation record, so it must be 0 as well. } - } - - // Now the situation is: - // [potentially skipped preceding...] | [LOWEST_LO...END] [FURTHER1...END] [FURTHER2...END]... [LAST...END] - // - // Check now if there is a record that directly succeeds the inserted one. - int next_succeeding = 0; // Default value to mark m_iNextLossGroupOffset, which is "no next record". - int eclipsed_length = 0; // This is to collect length of all removed records to be re-added with this one. - for (int loss_index = lowest_lo; loss_index != -1; loss_index = next_loss(loss_index)) - { - // We search for the first non-overlapping one. - if (loss_index > offset_hi) + // Anyways, we expect here no node data + if (p.m_iLossLength || p.m_iNextLossGroupOffset) { - next_succeeding = loss_index; - break; + os << "AT #" << i << ", group remain " << st.remain_loss_group << ", unexpected nonzero node data; "; + result = false; } - - // All indices in the middle should be cleared - index_to_clear.push_back(loss_index); - eclipsed_length += m_PktQueue[loss_index].m_iLossLength; - } - - // Now its: - // [preceding...] | [LOWEST_LO...END] [FURTHER1...END] [FURTHER2...END]... | [NEXT_SUCCEEDING...END]... - // or - // [preceding...] | [LOWEST_LO...END] [FURTHER1...END] [FURTHER2...END]... [LAST...END] - // - // next_succeeding is only the value to set to m_iNextLossGroupOffset. - // Length is determined by earliest index min(offset_lo, lowest_lo) and max(offset_hi, last_hi+length-1) - - - // Now clear everything in the records "in between", except that catch - // the farthest index ever seen in this range. - int furthest_hi = offset_hi; - for (size_t i = 0; i < index_to_clear.size(); ++i) - { - int x = index_to_clear[i]; - int hi = x + m_PktQueue[x].m_iLossLength - 1; - m_PktQueue[x].m_iLossLength = 0; - m_PktQueue[x].m_iNextLossGroupOffset = 0; - furthest_hi = std::max(furthest_hi, hi); } - if (lowest_lo != offset_lo) - { - // Clear the packet if it wasn't at the border - m_PktQueue[lowest_lo].m_iNextLossGroupOffset = 0; - m_PktQueue[lowest_lo].m_iLossLength = 0; - } - else + if (last_node != m_iLastRexmit) { - lowest_lo = min(lowest_lo, offset_lo); + os << "LAST found " << last_node << " != last=" << m_iLastRexmit << "; "; + result = false; } - loss_length = furthest_hi - lowest_lo + 1; - m_PktQueue[lowest_lo].m_iLossLength = loss_length; - m_PktQueue[lowest_lo].m_iNextLossGroupOffset = next_succeeding; + if (!result) + w_message = os.str(); - int new_length = m_iLossLengthCache - eclipsed_length + loss_length; + return result; - if (last_preceding_index == -1) - m_iFirstRexmit = lowest_lo; - else - m_PktQueue[last_preceding_index].m_iNextLossGroupOffset = lowest_lo; - m_iLossLengthCache = new_length; - - // Set the rexmit time only to the range that was requested to be inserted, - // even if this is effectively a fragment of a record. - set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); - return true; -} - -int32_t CSndBuffer::popLostSeq(DropRange& w_drop) -{ - static const DropRange nodrop = { {SRT_SEQNO_NONE, SRT_SEQNO_NONE}, SRT_MSGNO_CONTROL }; - w_drop = nodrop; - - ScopedLock bufferguard(m_BufLock); - - // In this version we don't predict any drop requests; - // the sequence is taken directly from the sender buffer, so there's - // physically no possibility to have any sequence lost that is not - // among the packets in the buffer. - - // Ok, this is our sequence to report. - int i = m_Packets.extractFirstLoss(); - if (i == -1) - return SRT_SEQNO_NONE; - - int32_t seqno = CSeqNo::incseq(m_iSndLastDataAck, i); - return seqno; } int SndPktArray::extractFirstLoss() @@ -1116,183 +1588,26 @@ int SndPktArray::extractFirstLoss() return -1; } -void CSndBuffer::removeLossUpTo(int32_t seqno) -{ - ScopedLock bufferguard(m_BufLock); - int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); - - if (offset < 0 || offset >= int(m_Packets.size())) - return; - - m_Packets.remove_loss(offset); -} - -int CSndBuffer::insertLoss(int32_t seqlo, int32_t seqhi, const time_point& pt) -{ - ScopedLock bufferguard(m_BufLock); - int offset_lo = CSeqNo::seqoff(m_iSndLastDataAck, seqlo); - int offset_hi = CSeqNo::seqoff(m_iSndLastDataAck, seqhi); - - return m_Packets.insert_loss(offset_lo, offset_hi, is_zero(pt) ? steady_clock::now(): pt); -} - -int CSndBuffer::getLossLength() -{ - return m_Packets.loss_length(); -} - -int CSndBuffer::getCurrBufSize() const -{ - return m_Packets.size(); -} - -int CSndBuffer::getMaxPacketLen() const -{ - return m_iBlockLen - m_iReservedSize; -} - -int CSndBuffer::countNumPacketsRequired(int iPldLen) const -{ - const int iPktLen = getMaxPacketLen(); - return number_slices(iPldLen, iPktLen); -} - -int CSndBuffer::getAvgBufSize(int& w_bytes, int& w_tsp) -{ - ScopedLock bufferguard(m_BufLock); /* Consistency of pkts vs. bytes vs. spantime */ - - /* update stats in case there was no add/ack activity lately */ - updAvgBufSize(steady_clock::now()); - - // Average number of packets and timespan could be small, - // so rounding is beneficial, while for the number of - // bytes in the buffer is a higher value, so rounding can be omitted, - // but probably better to round all three values. - w_bytes = m_mavg.bytes() + 0.49; - w_tsp = m_mavg.timespan_ms() + 0.49; - return int(m_mavg.pkts() + 0.49); -} - -void CSndBuffer::updAvgBufSize(const steady_clock::time_point& now) -{ - if (!m_mavg.isTimeToUpdate(now)) - return; - - int bytes = 0; - int timespan_ms = 0; - const int pkts = getBufferStats((bytes), (timespan_ms)); - m_mavg.update(now, pkts, bytes, timespan_ms); -} - -int CSndBuffer::getBufferStats(int& w_bytes, int& w_timespan) const -{ - w_bytes = m_iBytesCount; - /* - * Timespan can be less then 1000 us (1 ms) if few packets. - * Also, if there is only one pkt in buffer, the time difference will be 0. - * Therefore, always add 1 ms if not empty. - */ - if (m_Packets.empty()) - w_timespan = 0; - else - w_timespan = count_milliseconds(m_tsLastOriginTime - m_Packets[0].m_tsOriginTime) + 1; - - - return m_Packets.size(); -} - -CSndBuffer::duration CSndBuffer::getBufferingDelay(const time_point& tnow) const -{ - ScopedLock lck(m_BufLock); - - if (m_Packets.empty()) - return duration(0); - - return tnow - m_Packets[0].m_tsOriginTime; -} - -int CSndBuffer::dropLateData(int& w_bytes, int32_t& w_first_msgno, const steady_clock::time_point& too_late_time) -{ - ScopedLock bufferguard(m_BufLock); - - int dbytes = 0; - int32_t msgno = 0; - // Reach out to the position that is less than too_late_time, - // counting the bytes - size_t i; - for (i = 0; i < m_Packets.size(); ++i) - { - SndPktArray::Packet& p = m_Packets[i]; - // Stop on first busy or young enough - if (p.m_iBusy || p.m_tsOriginTime >= too_late_time) - break; - dbytes += p.m_iLength; - msgno = p.getMsgSeq(); - } - - // Now delete all these packets from the container - if (i) - { - // As the loop stopped on first busy, we ignore the return value - // because there should be no busy packets in this range. - m_Packets.pop(i); - - const int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, i); - m_iSndLastDataAck = fakeack; - } - - w_bytes = dbytes; // even if 0 - - // We report the increased number towards the last ever seen - // by the loop, as this last one is the last received. So remained - // (even if "should remain") is the first after the last removed one. - w_first_msgno = ++MsgNo(msgno); - - updAvgBufSize(steady_clock::now()); - - return i; -} - -int CSndBuffer::dropAll(int& w_bytes) -{ - ScopedLock bufferguard(m_BufLock); - // clear all - - int dpkts = m_Packets.size(); - m_Packets.clear(); - w_bytes = m_iBytesCount; - m_iBytesCount = 0; - - updAvgBufSize(steady_clock::now()); - return dpkts; -} - -CSndBuffer::~CSndBuffer() -{ - releaseMutex(m_BufLock); -} - -string CSndBuffer::show() const +// Debug support +string SndPktArray::show_external(int32_t seqno) const { using namespace hvu; ofmtbufstream out; int minw = 2; - if (m_Packets.size() > 99) + if (size() > 99) minw = 3; - else if (m_Packets.size() > 999) + else if (size() > 999) minw = 4; fmtc findex = fmtc().width(minw).fillzero(); - ScopedLock bufferguard(m_BufLock); - SndPktArray::PacketShowState st; - for (size_t i = 0; i < m_Packets.size(); ++i) + for (size_t i = 0; i < size(); ++i) { - int seqno = CSeqNo::incseq(m_iSndLastDataAck, i); + seqno = CSeqNo::incseq(seqno); out << "[" << fmt(i, findex) << "]%" << seqno << ": "; - m_Packets.showline(i, (st), (out)); + showline(i, (st), (out)); out.puts(); } diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index a06c27079..646ccb0f8 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -241,6 +241,8 @@ struct SndPktArray ~SndPktArray(); int unique_size() const { return m_iNewQueued; } + int first_loss() const { return m_iFirstRexmit; } + int last_loss() const { return m_iLastRexmit; } // GET, means the packet is still in the container, you just get access to it. // This call however changes the status of the retrieved packet by removing it @@ -264,7 +266,7 @@ struct SndPktArray bool clear_loss(int index); //void remove_loss_seq(int32_t seqhi); - bool insert_loss(int ixlo, int ixhi, const time_point&); + bool insert_loss(int ixlo, int ixhi, const time_point& = sync::steady_clock::now()); void set_rexmit_time(int ixlo, int ixhi, const time_point& time) { @@ -276,17 +278,7 @@ struct SndPktArray } } - int next_loss(int current_loss) - { - if (current_loss == -1) - return -1; - - Packet& p = m_PktQueue[current_loss]; - if (p.m_iNextLossGroupOffset == 0) - return -1; // The last loss - - return current_loss + p.m_iNextLossGroupOffset; - } + int next_loss(int current_loss); int loss_length() const { return m_iLossLengthCache; } @@ -304,6 +296,48 @@ struct SndPktArray Packet& operator[](size_t index) { return m_PktQueue[index]; } const Packet& operator[](size_t index) const { return m_PktQueue[index]; } + int setupNode(int first_node_index, int last_node_index, int next_node_index = -1) + { + int next_index_shift = next_node_index == -1 ? 0 : next_node_index - first_node_index; + m_PktQueue[first_node_index].m_iNextLossGroupOffset = next_index_shift; + m_PktQueue[first_node_index].m_iLossLength = last_node_index - first_node_index + 1; + return m_PktQueue[first_node_index].m_iLossLength; + } + + int getEndIndex(int first_index) + { + return first_index + m_PktQueue[first_index].m_iLossLength; + } + + int getLastIndex(int first_index) // will return -1 if not a node. + { + int end = getEndIndex(first_index); + return end == first_index ? -1 : end - 1; + } + + void linkPreviousNode(int previous_node_index, int next_node_index) + { + m_PktQueue[previous_node_index].m_iNextLossGroupOffset = next_node_index - previous_node_index; + } + + void clearNode(int x) + { + m_PktQueue[x].m_iLossLength = 0; + m_PktQueue[x].m_iNextLossGroupOffset = 0; + } + + // testing purposes + void clearAllLoss() + { + for (int loss = m_iFirstRexmit, next = loss; loss != -1; loss = next) + { + next = next_loss(loss); + clearNode(loss); + } + m_iFirstRexmit = -1; + m_iLastRexmit = -1; + } + // Helper state struct used in showline() only. struct PacketShowState { @@ -315,6 +349,11 @@ struct SndPktArray }; void showline(int index, PacketShowState& st, hvu::ofmtbufstream& out) const; + + std::string show_external(int32_t seqno) const; + + // Debug/assert only + bool validateLossIntegrity(std::string& msg); }; #else diff --git a/srtcore/common.h b/srtcore/common.h index c9cc0a9d8..830dcc044 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -91,14 +91,6 @@ modified by // Defined here because it relies on SRT_ASSERT macro provided in utilities.h #define SRT_ASSERT_AFFINITY(id) SRT_ASSERT(::srt::sync::CheckAffinity(id)) -// This is a log configuration used inside SRT. -// Applications using SRT, if they want to use the logging mechanism -// are free to create their own logger configuration objects for their -// own logger FA objects, or create their own. The object of this type -// is required to initialize the logger FA object. -namespace srt_logging { struct LogConfig; } -SRT_API extern srt_logging::LogConfig srt_logger_config; - namespace srt { diff --git a/test/test_buffer_snd.cpp b/test/test_buffer_snd.cpp index e21548fb5..3b0545830 100644 --- a/test/test_buffer_snd.cpp +++ b/test/test_buffer_snd.cpp @@ -1,5 +1,6 @@ #include "gtest/gtest.h" +#include "test_env.h" #include "buffer_snd.h" #include #include @@ -11,7 +12,7 @@ using namespace std; using namespace srt; using namespace srt::sync; -class TestSndBuffer: public testing::Test +class TestSndBuffer: public srt::Test { using time_point = steady_clock::time_point; @@ -124,14 +125,14 @@ class TestSndBuffer: public testing::Test protected: // SetUp() is run immediately before a test starts. - void SetUp() override + void setup() override { // make_unique is unfortunatelly C++14 m_buffer.reset(new CSndBuffer(32*1024, 1024, 1500, CPacket::udpHeaderSize(AF_INET), 0, 8192)); last_seqno = 12345; } - void TearDown() override + void teardown() override { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. @@ -140,6 +141,36 @@ class TestSndBuffer: public testing::Test }; + +class TestSndLoss: public srt::Test +{ + using time_point = steady_clock::time_point; + +public: + + hvu::ofmtrefstream sout {cout}; + + srt::SndPktArray packets { 1024, 20 }; + +protected: + + // SetUp() is run immediately before a test starts. + void setup() override + { + for (int i = 0; i < 20; ++i) + { + packets.push(); + } + } + + + void teardown() override + { + packets.clearAllLoss(); + } + +}; + TEST_F(TestSndBuffer, Basic) { for (int i = 1; i < 11; ++i) @@ -392,3 +423,766 @@ TEST_F(TestSndBuffer, Threaded) update_thread.join(); } + +// Tests for SndPktArray and loss management. Based on the test case description generated by Grok. + +TEST_F(TestSndLoss, Insert_into_empty_structure) +{ + // Initial: Empty (first=-1, last=-1, no nodes). + // (nothing to do) + + // Operation: Insert [5,7]. + + packets.insert_loss(5, 7); + + // Expected: Nodes = {5: len=3, next=0}; first=5, last=5. + EXPECT_EQ(packets[5].m_iLossLength, 3); + EXPECT_EQ(packets[5].m_iNextLossGroupOffset, 0); + + EXPECT_EQ(packets.first_loss(), 5); + EXPECT_EQ(packets.last_loss(), 5); + + EXPECT_EQ(packets.loss_length(), 3); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_disjoint_before_existing_AKA_becomes_new_first) +{ + // Initial: Nodes = {5: len=3, next=0}; first=5, last=5. + packets.insert_loss(5, 7); + + // Operation: Insert [1,2]. + packets.insert_loss(1, 2); + + // Expected: Nodes = {1: len=2, next=4}, {5: len=3, next=0}; first=1, last=5. + EXPECT_EQ(packets[1].m_iLossLength, 2); + EXPECT_EQ(packets[1].m_iNextLossGroupOffset, 4); + EXPECT_EQ(packets[5].m_iLossLength, 3); + EXPECT_EQ(packets[5].m_iNextLossGroupOffset, 0); + EXPECT_EQ(packets.first_loss(), 1); + EXPECT_EQ(packets.last_loss(), 5); + + EXPECT_EQ(packets.loss_length(), 5); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_disjoint_after_existing_AKA_becomes_new_last) +{ + + // Initial: Nodes = {1: len=2, next=4}, {5: len=3, next=0}; first=1, last=5. + packets.insert_loss(5, 7); + packets.insert_loss(1, 2); + + // Operation: Insert [10,12]. + packets.insert_loss(10, 12); + + // Expected: Nodes = {1: len=2, next=4}, {5: len=3, next=5}, {10: len=3, next=0}; first=1, last=10. + EXPECT_EQ(packets[1].m_iLossLength, 2); + EXPECT_EQ(packets[1].m_iNextLossGroupOffset, 4); + EXPECT_EQ(packets[5].m_iLossLength, 3); + EXPECT_EQ(packets[5].m_iNextLossGroupOffset, 5); + EXPECT_EQ(packets[10].m_iLossLength, 3); + EXPECT_EQ(packets[10].m_iNextLossGroupOffset, 0); + EXPECT_EQ(packets.first_loss(), 1); + EXPECT_EQ(packets.last_loss(), 10); + + EXPECT_EQ(packets.loss_length(), 8); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_disjoint_in_middle_gap) +{ + + // Initial: Nodes = {1: len=2, next=4}, {5: len=3, next=5}, {10: len=3, next=0}; first=1, last=10. + packets.insert_loss(10, 12); + packets.insert_loss(1, 2); + packets.insert_loss(5, 6); + + // Operation: Insert [8,8]. + packets.insert_loss(8, 8); + + // Expected: Nodes = {1: len=2, next=4}, {5: len=2, next=3}, {8: len=1, next=2}, {10: len=3, next=0}; first=1, last=10. + EXPECT_EQ(packets[1].m_iLossLength, 2); + EXPECT_EQ(packets[1].m_iNextLossGroupOffset, 4); + + EXPECT_EQ(packets[5].m_iLossLength, 2); + EXPECT_EQ(packets[5].m_iNextLossGroupOffset, 3); + + EXPECT_EQ(packets[8].m_iLossLength, 1); + EXPECT_EQ(packets[8].m_iNextLossGroupOffset, 2); + + EXPECT_EQ(packets[10].m_iLossLength, 3); + EXPECT_EQ(packets[10].m_iNextLossGroupOffset, 0); + + EXPECT_EQ(packets.first_loss(), 1); + EXPECT_EQ(packets.last_loss(), 10); + + EXPECT_EQ(packets.loss_length(), 8); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_adjacent_left_of_existing_AKA_merge) +{ + // Initial: Nodes = {5: len=3, next=0}; first=5, last=5. + packets.insert_loss(5, 7); + + // Operation: Insert [4,4]. + packets.insert_loss(4, 4); + + // Expected: Nodes = {4: len=4, next=0}; first=4, last=4. + EXPECT_EQ(packets[4].m_iLossLength, 4); + + EXPECT_EQ(packets.loss_length(), 4); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_adjacent_right_of_existing_AKA_merge) +{ + // Initial: Nodes = {5: len=3, next=0}; first=5, last=5. + packets.insert_loss(5, 7); + + // Operation: Insert [8,8]. + packets.insert_loss(8, 8); + + // Expected: Nodes = {5: len=4, next=0}; first=5, last=5. + EXPECT_EQ(packets[5].m_iLossLength, 4); + EXPECT_EQ(packets[5].m_iNextLossGroupOffset, 0); + EXPECT_EQ(packets[8].m_iLossLength, 0); + EXPECT_EQ(packets.first_loss(), 5); + EXPECT_EQ(packets.last_loss(), 5); + + EXPECT_EQ(packets.loss_length(), 4); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_overlapping_left_of_existing_AKA_extend_left) +{ + // Initial: Nodes = {5: len=3, next=0}; first=5, last=5. + packets.insert_loss(5, 7); + + // Operation: Insert [3,6]. + packets.insert_loss(3, 6); + + // Expected: Nodes = {3: len=5, next=0}; first=3, last=3. + EXPECT_EQ(packets[3].m_iLossLength, 5); + EXPECT_EQ(packets[3].m_iNextLossGroupOffset, 0); + EXPECT_EQ(packets[5].m_iLossLength, 0); + EXPECT_EQ(packets.first_loss(), 3); + EXPECT_EQ(packets.last_loss(), 3); + + EXPECT_EQ(packets.loss_length(), 5); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_overlapping_right_of_existing_AKA_extend_right) +{ + // Initial: Nodes = {5: len=3, next=0}; first=5, last=5. + packets.insert_loss(5, 7); + + // Operation: Insert [6,9]. + packets.insert_loss(6, 9); + + // Expected: Nodes = {5: len=5, next=0}; first=5, last=5. + EXPECT_EQ(packets[5].m_iLossLength, 5); + EXPECT_EQ(packets[5].m_iNextLossGroupOffset, 0); + EXPECT_EQ(packets[6].m_iLossLength, 0); + EXPECT_EQ(packets.first_loss(), 5); + EXPECT_EQ(packets.last_loss(), 5); + + EXPECT_EQ(packets.loss_length(), 5); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_completely_covering_existing_AKA_swallow) +{ + // Initial: Nodes = {5: len=3, next=0}; first=5, last=5. + packets.insert_loss(5, 7); + + // Operation: Insert [4,8]. + packets.insert_loss(4, 8); + + // Expected: Nodes = {4: len=5, next=0}; first=4, last=4. + EXPECT_EQ(packets[4].m_iLossLength, 5); + EXPECT_EQ(packets[5].m_iLossLength, 0); + EXPECT_EQ(packets.first_loss(), 4); + EXPECT_EQ(packets.last_loss(), 4); + + EXPECT_EQ(packets.loss_length(), 5); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_inside_existing_AKA_no_change) +{ + // Initial: Nodes = {5: len=3, next=0}; first=5, last=5. + packets.insert_loss(5, 7); + + // Operation: Insert [6,6]. + packets.insert_loss(6, 6); + + // Expected: No change; first=5, last=5. + EXPECT_EQ(packets[5].m_iLossLength, 3); + EXPECT_EQ(packets[5].m_iNextLossGroupOffset, 0); + EXPECT_EQ(packets[6].m_iLossLength, 0); + + EXPECT_EQ(packets.first_loss(), 5); + EXPECT_EQ(packets.last_loss(), 5); + + EXPECT_EQ(packets.loss_length(), 3); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_inside_existing_AKA_no_change_2) +{ + // Initial: Nodes = {5: len=3, next=0}; first=5, last=5. + packets.insert_loss(1, 11); + EXPECT_EQ(packets[1].m_iLossLength, 11); + EXPECT_EQ(packets[1].m_iNextLossGroupOffset, 0); + + // Operation: Insert [6,6]. + packets.insert_loss(6, 6); + packets.insert_loss(9, 10); + packets.insert_loss(3, 9); + + // Expected: No change; first=5, last=5. + EXPECT_EQ(packets[1].m_iLossLength, 11); + EXPECT_EQ(packets[1].m_iNextLossGroupOffset, 0); + EXPECT_EQ(packets[3].m_iLossLength, 0); + EXPECT_EQ(packets[6].m_iLossLength, 0); + EXPECT_EQ(packets[9].m_iLossLength, 0); + + EXPECT_EQ(packets.first_loss(), 1); + EXPECT_EQ(packets.last_loss(), 1); + + EXPECT_EQ(packets.loss_length(), 11); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_bridging_two_disjoint_ranges_AKA_merge) +{ + // Initial: Nodes = {1: len=2, next=4}, {5: len=3, next=0}; first=1, last=5. + packets.insert_loss(1, 2); + packets.insert_loss(5, 7); + EXPECT_EQ(packets.first_loss(), 1); + EXPECT_EQ(packets.last_loss(), 5); + + // Operation: Insert [3,4]. + packets.insert_loss(3, 4); + + // Expected: Nodes = {1: len=7, next=0}; first=1, last=1. + + // (first: expect removed nodes at 3 and 5 + EXPECT_EQ(packets[3].m_iLossLength, 0); + EXPECT_EQ(packets[5].m_iLossLength, 0); + + // Now valid node + EXPECT_EQ(packets[1].m_iLossLength, 7); + EXPECT_EQ(packets[1].m_iNextLossGroupOffset, 0); + EXPECT_EQ(packets.first_loss(), 1); + EXPECT_EQ(packets.last_loss(), 1); + + EXPECT_EQ(packets.loss_length(), 7); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_overlapping_and_bridging_multiple_ranges) +{ + // Initial: Nodes = {1: len=2, next=4}, {5: len=3, next=5}, {10: len=3, next=0}; first=1, last=10. + packets.insert_loss(10, 12); + packets.insert_loss(5, 7); + packets.insert_loss(1, 2); + + EXPECT_EQ(packets[1].m_iLossLength, 2); + EXPECT_EQ(packets[1].m_iNextLossGroupOffset, 4); + + EXPECT_EQ(packets[5].m_iLossLength, 3); + EXPECT_EQ(packets[5].m_iNextLossGroupOffset, 5); + + EXPECT_EQ(packets[10].m_iLossLength, 3); + EXPECT_EQ(packets[10].m_iNextLossGroupOffset, 0); + + // Operation: Insert [4,11]. + packets.insert_loss(4, 11); + + // Expected: Nodes = {1: len=2, next=3}, {4: len=9, next=0}; first=1, last=4. (Merges second and third, overlaps first's adjacent.) + EXPECT_EQ(packets[1].m_iLossLength, 2); + EXPECT_EQ(packets[1].m_iNextLossGroupOffset, 3); + EXPECT_EQ(packets[4].m_iLossLength, 9); + EXPECT_EQ(packets[4].m_iNextLossGroupOffset, 0); + EXPECT_EQ(packets.first_loss(), 1); + EXPECT_EQ(packets.last_loss(), 4); + + EXPECT_EQ(packets.loss_length(), 11); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_swallowing_multiple_ranges) +{ + // Initial: Nodes = {1: len=2, next=4}, {5: len=3, next=5}, {10: len=3, next=0}; first=1, last=10. + packets.insert_loss(10, 12); + packets.insert_loss(5, 7); + packets.insert_loss(1, 2); + + EXPECT_EQ(packets[1].m_iLossLength, 2); + EXPECT_EQ(packets[1].m_iNextLossGroupOffset, 4); + + EXPECT_EQ(packets[5].m_iLossLength, 3); + EXPECT_EQ(packets[5].m_iNextLossGroupOffset, 5); + + EXPECT_EQ(packets[10].m_iLossLength, 3); + EXPECT_EQ(packets[10].m_iNextLossGroupOffset, 0); + + // Operation: Insert [0,15]. + packets.insert_loss(0, 15); + + // Expect first that none of the old nodes exists anymore. + EXPECT_EQ(packets[1].m_iLossLength, 0); + EXPECT_EQ(packets[1].m_iNextLossGroupOffset, 0); + + EXPECT_EQ(packets[5].m_iLossLength, 0); + EXPECT_EQ(packets[5].m_iNextLossGroupOffset, 0); + + EXPECT_EQ(packets[10].m_iLossLength, 0); + EXPECT_EQ(packets[10].m_iNextLossGroupOffset, 0); + + // Expected: Nodes = {0: len=16, next=0}; first=0, last=0. + EXPECT_EQ(packets[0].m_iLossLength, 16); + EXPECT_EQ(packets[0].m_iNextLossGroupOffset, 0); + + // The only node + EXPECT_EQ(packets.first_loss(), 0); + EXPECT_EQ(packets.last_loss(), 0); + + EXPECT_EQ(packets.loss_length(), 16); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_at_absolute_front_AKA_index_0_disjoint) +{ + // Initial: Nodes = {5: len=3, next=0}; first=5, last=5. + packets.insert_loss(5, 7); + EXPECT_EQ(packets[5].m_iLossLength, 3); + + // Operation: Insert [0,0]. + packets.insert_loss(0, 0); + + // Expected: Nodes = {0: len=1, next=5}, {5: len=3, next=0}; first=0, last=5. + EXPECT_EQ(packets[0].m_iLossLength, 1); + EXPECT_EQ(packets[0].m_iNextLossGroupOffset, 5); + + EXPECT_EQ(packets[5].m_iLossLength, 3); + EXPECT_EQ(packets[5].m_iNextLossGroupOffset, 0); + + EXPECT_EQ(packets.first_loss(), 0); + EXPECT_EQ(packets.last_loss(), 5); + + EXPECT_EQ(packets.loss_length(), 4); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_at_absolute_end_AKA_last_index_disjoint) +{ + // Initial: Nodes = {5: len=3, next=0}; first=5, last=5. + packets.insert_loss(5, 7); + EXPECT_EQ(packets[5].m_iLossLength, 3); + + // Operation: Insert [19,19]. + packets.insert_loss(19, 19); + + // Expected: Nodes = {5: len=3, next=14}, {19: len=1, next=0}; first=5, last=19. + EXPECT_EQ(packets[5].m_iLossLength, 3); + EXPECT_EQ(packets[5].m_iNextLossGroupOffset, 14); + + EXPECT_EQ(packets[19].m_iLossLength, 1); + EXPECT_EQ(packets[19].m_iNextLossGroupOffset, 0); + + EXPECT_EQ(packets.first_loss(), 5); + EXPECT_EQ(packets.last_loss(), 19); + + EXPECT_EQ(packets.loss_length(), 4); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_full_deque_range_over_empty) +{ + // Initial: Empty. + // Operation: Insert [0,19]. + packets.insert_loss(0, 19); + + // Expected: Nodes = {0: len=20, next=0}; first=0, last=0. + EXPECT_EQ(packets[0].m_iLossLength, 20); + EXPECT_EQ(packets[0].m_iNextLossGroupOffset, 0); + + EXPECT_EQ(packets.first_loss(), 0); + EXPECT_EQ(packets.last_loss(), 0); + + EXPECT_EQ(packets.loss_length(), 20); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Insert_adjacent_or_overlapping_when_updating_last_only) +{ + // Initial: Nodes = {1: len=2, next=0}; first=1, last=1. + packets.insert_loss(1, 2); + + // Operation: Insert [3,4]. (Adjacent, merge if policy allows; assume merge for contiguous). + packets.insert_loss(3, 4); + + // Expected: Nodes = {1: len=4, next=0}; first=1, last=1. + EXPECT_EQ(packets[1].m_iLossLength, 4); + EXPECT_EQ(packets[1].m_iNextLossGroupOffset, 0); + + EXPECT_EQ(packets[3].m_iLossLength, 0); + + EXPECT_EQ(packets.first_loss(), 1); + EXPECT_EQ(packets.last_loss(), 1); + + EXPECT_EQ(packets.loss_length(), 4); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + + +// POP single item removal tests + +TEST_F(TestSndLoss, Remove_from_single_range_with_len_1_AKA_empties_structure) +{ + // Initial: Nodes = {5: len=1, next=0}; first=5, last=5. + packets.insert_loss(5, 5); + + // Operation: Remove single first. + int first_loss = packets.extractFirstLoss(); + EXPECT_EQ(first_loss, 5); + + // Expected: Empty; first=-1, last=-1. + EXPECT_EQ(packets.loss_length(), 0); + EXPECT_EQ(packets.first_loss(), -1); + EXPECT_EQ(packets.last_loss(), -1); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Remove_from_single_range_with_len_gt_1_AKA_shrink_left) +{ + // Initial: Nodes = {5: len=3, next=0}; first=5, last=5. + packets.insert_loss(5, 7); + + // Operation: Remove single first. + int first_loss = packets.extractFirstLoss(); + EXPECT_EQ(first_loss, 5); + + // Expected: Nodes = {6: len=2, next=0}; first=6, last=6. (Implicit len=0 at old 5 position.) + EXPECT_EQ(packets.loss_length(), 2); + EXPECT_EQ(packets.first_loss(), 6); + EXPECT_EQ(packets.last_loss(), 6); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Remove_from_first_range_len_1_multiple_ranges_AKA_update_first) +{ + // Initial: Nodes = {1: len=1, next=4}, {5: len=3, next=0}; first=1, last=5. + packets.insert_loss(5, 7); + packets.insert_loss(1, 1); + + // Operation: Remove single first. + int first_loss = packets.extractFirstLoss(); + EXPECT_EQ(first_loss, 1); + + // EXPECT: removed nodes + EXPECT_EQ(packets[1].m_iLossLength, 0); + + // Expected: Nodes = {5: len=3, next=0}; first=5, last=5. + EXPECT_EQ(packets[5].m_iLossLength, 3); + + EXPECT_EQ(packets.loss_length(), 3); + EXPECT_EQ(packets.first_loss(), 5); + EXPECT_EQ(packets.last_loss(), 5); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Remove_from_first_range_len_gt_1_multiple_ranges_AKA_shrink_last_unchanged) +{ + // Initial: Nodes = {1: len=3, next=5}, {8: len=2, next=0}; first=1, last=8. + packets.insert_loss(1, 3); + packets.insert_loss(8, 9); + + // Operation: Remove single first. + int first_loss = packets.extractFirstLoss(); + EXPECT_EQ(first_loss, 1); + + // EXPECT: removed nodes + EXPECT_EQ(packets[1].m_iLossLength, 0); + + // Expected: Nodes = {2: len=2, next=6}, {8: len=2, next=0}; first=2, last=8. (Next updated: 2 to 8 offset=6 > 2.) + EXPECT_EQ(packets[2].m_iLossLength, 2); + EXPECT_EQ(packets[2].m_iNextLossGroupOffset, 6); + + EXPECT_EQ(packets[8].m_iLossLength, 2); + EXPECT_EQ(packets[8].m_iNextLossGroupOffset, 0); + + EXPECT_EQ(packets.first_loss(), 2); + EXPECT_EQ(packets.last_loss(), 8); + + EXPECT_EQ(packets.loss_length(), 4); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Remove_when_only_one_marked_element_overall) +{ + // Initial: Nodes = {0: len=1, next=0}; first=0, last=0. + + packets.insert_loss(0, 0); + + // Operation: Remove single first. + int first_loss = packets.extractFirstLoss(); + EXPECT_EQ(first_loss, 0); + + // Expected: Empty; first=-1, last=-1. + EXPECT_EQ(packets.loss_length(), 0); + EXPECT_EQ(packets.first_loss(), -1); + EXPECT_EQ(packets.last_loss(), -1); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Remove_when_removal_affects_last_AKA_single_range_len_2) +{ + // Initial: Nodes = {5: len=2, next=0}; first=5, last=5. + packets.insert_loss(5, 6); + + // Operation: Remove single first. + int first_loss = packets.extractFirstLoss(); + EXPECT_EQ(first_loss, 5); + + // Expected: Nodes = {6: len=1, next=0}; first=6, last=6. + EXPECT_EQ(packets[5].m_iLossLength, 0); + EXPECT_EQ(packets[6].m_iLossLength, 1); + + EXPECT_EQ(packets.loss_length(), 1); + EXPECT_EQ(packets.first_loss(), 6); + EXPECT_EQ(packets.last_loss(), 6); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +// Remove multiple tests + +TEST_F(TestSndLoss, Remove_M_lt_first_range_len_AKA_partial_shrink_left) +{ + // Initial: Nodes = {5: len=5_ next=0}; first=5_ last=5. M=2. + packets.insert_loss(5, 9); + + // Operation: Remove first 2 marked. + + // NOTE: AI Bot didn't understand that removal is per packet + // index, not loss number - remove loss from as many packets + // as needed so that 5, 6 sequences are hooked up. + packets.remove_loss(6); + + // Expected: Nodes = {7: len=3_ next=0}; first=7_ last=7. + EXPECT_EQ(packets.first_loss(), 7); + EXPECT_EQ(packets.last_loss(), 7); + EXPECT_EQ(packets[5].m_iLossLength, 0); + EXPECT_EQ(packets[7].m_iLossLength, 3); + EXPECT_EQ(packets.loss_length(), 3); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} +TEST_F(TestSndLoss, Remove_M_equal_first_range_len_AKA_remove_entire_first_range) +{ + // Initial: Nodes = {5: len=3_ next=5}_ {10: len=2_ next=0}; first=5_ last=10. M=3. + packets.insert_loss(5, 7); + packets.insert_loss(10, 12); + + // Operation: Remove first 3 marked. + // AI-FIX: intended is that removed are 3 subsequent losses, + // so remove all up to 9 + packets.remove_loss(9); + + // AI GENERATION: + // Expected: Nodes = {10: len=2_ next=0}; first=10_ last=10. + // Expected: Nodes = {7: len=3_ next=0}; first=7_ last=7. + // + // Ok, this is bullshit; when removed up to 9, it should + // clear the first record and leave untouchted the second one, + // so it's 10-12 the only remaining loss. + + EXPECT_EQ(packets.first_loss(), 10); + EXPECT_EQ(packets.last_loss(), 10); + EXPECT_EQ(packets[5].m_iLossLength, 0); + EXPECT_EQ(packets[10].m_iLossLength, 3); + EXPECT_EQ(packets.loss_length(), 3); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} +TEST_F(TestSndLoss, Remove_M_gt_first_range_len_AKA_remove_first_AND_partial_next) +{ + // Initial: Nodes = {1: len=2_ next=4}_ {5: len=3_ next=0}; first=1_ last=5. M=4. + packets.insert_loss(1, 2); + packets.insert_loss(5, 7); + + // AI: + // Operation: Remove first 4 marked. AKA_Remove [1-2] + first 2 of [5-7] + // Expected: Nodes = {7: len=1_ next=0}; first=7_ last=7. + // REAL: + // Operation: remove up to 5. should remain 6-7. + packets.remove_loss(5); + // So, expected clear node 1 and 5, activated 6 with len=2 + EXPECT_EQ(packets[1].m_iLossLength, 0); + EXPECT_EQ(packets[5].m_iLossLength, 0); + EXPECT_EQ(packets[6].m_iLossLength, 2); + EXPECT_EQ(packets.first_loss(), 6); + EXPECT_EQ(packets.last_loss(), 6); + + EXPECT_EQ(packets.loss_length(), 2); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} +TEST_F(TestSndLoss, Remove_across_multiple_full_ranges) +{ + // Initial: Nodes = {1: len=2_ next=4}_ {5: len=3_ next=5}_ {10: len=2_ next=0}; first=1_ last=10. M=5. + packets.insert_loss(1, 2); + packets.insert_loss(5, 7); + packets.insert_loss(10, 12); + + // AI: + // Operation: Remove first 5 marked. AKA_Full first + full second + // Expected: Nodes = {10: len=2_ next=0}; first=10_ last=10. + // REAL: + // Ok, so let's remove up to 8 so that node 10 is left untouched. + packets.remove_loss(8); + + EXPECT_EQ(packets.first_loss(), 10); + EXPECT_EQ(packets.last_loss(), 10); + EXPECT_EQ(packets[1].m_iLossLength, 0); + EXPECT_EQ(packets[5].m_iLossLength, 0); + EXPECT_EQ(packets[10].m_iLossLength, 3); + EXPECT_EQ(packets.loss_length(), 3); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} +TEST_F(TestSndLoss, Remove_all_marked_elements_AKA_empties_structure) +{ + // Initial: Nodes = {1: len=2_ next=4}_ {5: len=3_ next=0}; first=1_ last=5. M=5. + packets.insert_loss(1, 2); + packets.insert_loss(5, 7); + + // Operation: Remove first 5 marked. + // Expected: Empty; first=-1_ last=-1. + // REAL: Ok, this time let's remove up to the exact element. + // Should be empty afterwards. + packets.remove_loss(7); + + EXPECT_EQ(packets.first_loss(), -1); + EXPECT_EQ(packets.last_loss(), -1); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + +TEST_F(TestSndLoss, Build_complex_then_remove_single_and_prefix) +{ + // Start empty. Insert [1,2], [5,7], [10,10]. (Disjoint). + packets.insert_loss(1, 2); + packets.insert_loss(5, 7); + packets.insert_loss(10, 10); + + // Expected after inserts: {1: len=2, next=4}, {5: len=3, next=5}, {10: len=1, next=0}; first=1, last=10. (Gaps ensure next > len.) + EXPECT_EQ(packets.first_loss(), 1); + EXPECT_EQ(packets.last_loss(), 10); + EXPECT_EQ(packets[1].m_iLossLength, 2); + EXPECT_EQ(packets[5].m_iLossLength, 3); + EXPECT_EQ(packets[10].m_iLossLength, 1); + EXPECT_EQ(packets.loss_length(), 6); + + std::string validmsg; + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; + + // Insert [3,6]. (Bridge + overlap + adjacent). Expected: {1: len=7, next=9}, {10: len=1, next=0}; first=1, last=10. (Merge to [1-7]; 9 > 7.) + packets.insert_loss(3, 6); + + EXPECT_EQ(packets.first_loss(), 1); + EXPECT_EQ(packets.last_loss(), 10); + EXPECT_EQ(packets[1].m_iLossLength, 7); + EXPECT_EQ(packets[5].m_iLossLength, 0); + EXPECT_EQ(packets[10].m_iLossLength, 1); + EXPECT_EQ(packets.loss_length(), 8); + + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; + + // Remove single first. Expected: {2: len=6, next=8}, {10: len=1, next=0}; first=2, last=10. (8 > 6.) + int first_loss = packets.extractFirstLoss(); + EXPECT_EQ(first_loss, 1); + EXPECT_EQ(packets.first_loss(), 2); + EXPECT_EQ(packets.last_loss(), 10); + EXPECT_EQ(packets[1].m_iLossLength, 0); + EXPECT_EQ(packets[2].m_iLossLength, 6); + EXPECT_EQ(packets[2].m_iNextLossGroupOffset, 8); + EXPECT_EQ(packets[10].m_iLossLength, 1); + EXPECT_EQ(packets.loss_length(), 7); + + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; + + // Remove first 3 marked. Expected: {5: len=3, next=5}, {10: len=1, next=0}; first=5, last=10. (5 > 3.) + packets.remove_loss(4); + EXPECT_EQ(packets.first_loss(), 5); + EXPECT_EQ(packets.last_loss(), 10); + EXPECT_EQ(packets[1].m_iLossLength, 0); + EXPECT_EQ(packets[2].m_iLossLength, 0); + EXPECT_EQ(packets[5].m_iLossLength, 3); + EXPECT_EQ(packets[10].m_iLossLength, 1); + EXPECT_EQ(packets.loss_length(), 4); + + EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; +} + + + + From c9d1ec604e24c64fda47fd6743bd160d51b25bf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 4 Dec 2025 13:48:20 +0100 Subject: [PATCH 15/26] Fixed some build breaks --- srtcore/buffer_snd.cpp | 6 +++--- test/test_buffer_snd.cpp | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index d8fdde873..0b6370a52 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -495,7 +495,7 @@ void CSndBuffer::releasePacket(int32_t seqno) m_iSndLastDataAck = CSeqNo::incseq(m_iSndLastDataAck, removed); HLOGC(bslog.Debug, log << "CSndBuffer::releasePacket %" << seqno << ": ACK-revoked " << removed << " more packets up to %" << m_iSndLastDataAck); - logged = true; + IF_HEAVY_LOGGING(logged = true); } } IF_HEAVY_LOGGING(if (!logged) LOGC(bslog.Debug, log << "CSndBuffer::releasePacket: %" << seqno << ": non+ pkts revoked")); @@ -745,7 +745,7 @@ string CSndBuffer::show() const int seqno = CSeqNo::incseq(m_iSndLastDataAck, i); out << "[" << fmt(i, findex) << "]%" << seqno << ": "; m_Packets.showline(i, (st), (out)); - out.puts(); + out.base() << endl; } return out.str(); @@ -1608,7 +1608,7 @@ string SndPktArray::show_external(int32_t seqno) const seqno = CSeqNo::incseq(seqno); out << "[" << fmt(i, findex) << "]%" << seqno << ": "; showline(i, (st), (out)); - out.puts(); + out.base() << endl; } return out.str(); diff --git a/test/test_buffer_snd.cpp b/test/test_buffer_snd.cpp index 3b0545830..c973187db 100644 --- a/test/test_buffer_snd.cpp +++ b/test/test_buffer_snd.cpp @@ -184,12 +184,12 @@ TEST_F(TestSndBuffer, Basic) // Now let's read 3 packets from it CPacket rpkt; - EXPECT_NE(readUniqueForget(), 0); - EXPECT_NE(readUniqueForget(), 0); + EXPECT_NE(readUniqueForget(), 0u); + EXPECT_NE(readUniqueForget(), 0u); { CSndPacket spkt; - EXPECT_NE(readUniqueKeep((spkt)), 0); + EXPECT_NE(readUniqueKeep((spkt)), 0u); } // And let's see @@ -225,12 +225,12 @@ TEST_F(TestSndBuffer, Basic) sout.puts(m_buffer->show()); // Now read 4 more packets - EXPECT_NE(readUniqueForget(), 0); - EXPECT_NE(readUniqueForget(), 0); - EXPECT_NE(readUniqueForget(), 0); + EXPECT_NE(readUniqueForget(), 0u); + EXPECT_NE(readUniqueForget(), 0u); + EXPECT_NE(readUniqueForget(), 0u); { CSndPacket spkt; - EXPECT_NE(readUniqueKeep(spkt), 0); + EXPECT_NE(readUniqueKeep(spkt), 0u); } // Then add two rexmit requests From 5fc71801663dcce6b458533e3d72639af5ec4426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 4 Dec 2025 13:57:39 +0100 Subject: [PATCH 16/26] Fixed some build breaks (test) --- test/test_buffer_snd.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_buffer_snd.cpp b/test/test_buffer_snd.cpp index c973187db..ec0acd932 100644 --- a/test/test_buffer_snd.cpp +++ b/test/test_buffer_snd.cpp @@ -205,7 +205,7 @@ TEST_F(TestSndBuffer, Basic) { // Now read one packet as old at seq 12346, and while keeping it, ACK up to 12347. CSndPacket snd; - EXPECT_NE(readOld(12346, (snd)), 0); + EXPECT_NE(readOld(12346, (snd)), 0u); revokeSeq(12347); // SHOULD ACK only up to 12346. From 05a4f031a630e22914e6ffb84ac7d828f2e3185e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 4 Dec 2025 15:42:56 +0100 Subject: [PATCH 17/26] Restored working code with the old CSndBuffer --- srtcore/buffer_snd.cpp | 24 +++++++++++------------- test/test_buffer_snd.cpp | 3 ++- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 0b6370a52..e7a67617f 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -1910,7 +1910,7 @@ int CSndBuffer::extractUniquePacket(CSndPacket& w_packet, steady_clock::time_poi HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: picked up packet to send: size=" << readlen << " #" << w_packet.pkt.getMsgSeq() << " %" << w_packet.pkt.seqno() - << " !" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); + << " !" << BufferStamp(w_packet.pkt.m_pcData, w_packet.pkt.getLength())); break; } @@ -1918,7 +1918,7 @@ int CSndBuffer::extractUniquePacket(CSndPacket& w_packet, steady_clock::time_poi return readlen; } -void CSndBuffer::releasePacket(int32_t seqno) +void CSndBuffer::releasePacket(int32_t /*seqno*/) { // only debug } @@ -1979,7 +1979,7 @@ int32_t CSndBuffer::getMsgNoAtSeq(const int32_t seqno) return p->getMsgSeq(); } -int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) +int CSndBuffer::readOldPacket(int32_t seqno, CSndPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) { ScopedLock bufferguard(m_BufLock); @@ -2003,11 +2003,11 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti int32_t last_seq = p->m_iSeqNo; #endif - w_packet.set_seqno(seqno); + w_packet.pkt.set_seqno(seqno); // This is rexmit request, so the packet should have the sequence number // already set when it was once sent uniquely. - SRT_ASSERT(p->m_iSeqNo == w_packet.seqno()); + SRT_ASSERT(p->m_iSeqNo == w_packet.pkt.seqno()); // Check if the block that is the next candidate to send (m_pCurrBlock pointing) is stale. @@ -2051,8 +2051,8 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti // to simply take the sequence number from the block. But this is a new // feature and should be only used after refax for the sender buffer to // make it manage the sequence numbers inside, instead of by CUDT::m_iSndLastDataAck. - w_drop.seqno[DropRange::BEGIN] = w_packet.seqno(); - w_drop.seqno[DropRange::END] = CSeqNo::incseq(w_packet.seqno(), msglen - 1); + w_drop.seqno[DropRange::BEGIN] = w_packet.pkt.seqno(); + w_drop.seqno[DropRange::END] = CSeqNo::incseq(w_packet.pkt.seqno(), msglen - 1); m_SndLossList.removeUpTo(w_drop.seqno[DropRange::END]); @@ -2065,16 +2065,16 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti return READ_DROP; } - w_packet.m_pcData = p->m_pcData; + w_packet.pkt.m_pcData = p->m_pcData; const int readlen = p->m_iLength; - w_packet.setLength(readlen, m_iBlockLen); + w_packet.pkt.setLength(readlen, m_iBlockLen); // XXX Here the value predicted to be applied to PH_MSGNO field is extracted. // As this function is predicted to extract the data to send as a rexmited packet, // the packet must be in the form ready to send - so, in case of encryption, // encrypted, and with all ENC flags already set. So, the first call to send // the packet originally (extractUniquePacket) must set these flags first. - w_packet.set_msgflags(p->m_iMsgNoBitset); + w_packet.pkt.set_msgflags(p->m_iMsgNoBitset); w_srctime = p->m_tsOriginTime; // This function is called when packet retransmission is triggered. @@ -2082,7 +2082,7 @@ int CSndBuffer::readOldPacket(int32_t seqno, CPacket& w_packet, steady_clock::ti p->m_tsRexmitTime = steady_clock::now(); HLOGC(bslog.Debug, - log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.seqno() + log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.pkt.seqno() << " size=" << readlen << " to send [REXMIT]"); return readlen; @@ -2476,8 +2476,6 @@ std::string CSndBuffer::show() const // Stubs, unused in old buffer -bool CSndBuffer::reserveSeqno(int32_t ) { return true; } -bool CSndBuffer::releaseSeqno(int32_t ) { return true; } bool CSndBuffer::cancelLostSeq(int32_t) { return false; } #endif diff --git a/test/test_buffer_snd.cpp b/test/test_buffer_snd.cpp index ec0acd932..14b8a3639 100644 --- a/test/test_buffer_snd.cpp +++ b/test/test_buffer_snd.cpp @@ -141,6 +141,7 @@ class TestSndBuffer: public srt::Test }; +#if SRT_SNDBUF_NEW class TestSndLoss: public srt::Test { @@ -1183,6 +1184,6 @@ TEST_F(TestSndLoss, Build_complex_then_remove_single_and_prefix) EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; } - +#endif From ee5ef5812106bcb2abc8796b4a5cef0c593eaa65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 4 Dec 2025 17:48:23 +0100 Subject: [PATCH 18/26] Fixed preallocation and maximum storage capacity parameters --- srtcore/buffer_snd.cpp | 18 +++++++++++++----- srtcore/buffer_snd.h | 6 +++--- srtcore/socketconfig.cpp | 7 +++++++ test/test_buffer_snd.cpp | 2 +- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index e7a67617f..4bb6abd8a 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -66,7 +66,7 @@ using namespace std; using namespace srt::logging; using namespace sync; -CSndBuffer::CSndBuffer(size_t bytesize, size_t slicesize, size_t mss, size_t headersize, size_t reservedsize, int flwsize SRT_ATR_UNUSED) : +CSndBuffer::CSndBuffer(size_t pktsize, size_t slicesize, size_t mss, size_t headersize, size_t reservedsize, int flwsize SRT_ATR_UNUSED) : m_BufLock(), m_iBlockLen(mss - headersize), m_iReservedSize(reservedsize), @@ -75,11 +75,11 @@ CSndBuffer::CSndBuffer(size_t bytesize, size_t slicesize, size_t mss, size_t hea m_iNextMsgNo(1), m_iBytesCount(0), #if SRT_SNDBUF_NEW - m_iCapacity(number_slices(bytesize, m_iBlockLen)), + m_iCapacity(pktsize), // To avoid performance degradation during the transmission, // we allocate in advance all required blocks so that they are // picked up from the storage when required. - m_Packets(m_iBlockLen, m_iCapacity) + m_Packets(m_iBlockLen, m_iCapacity, slicesize) #else m_SndLossList(flwsize * 2), m_pBlock(NULL), @@ -91,12 +91,11 @@ CSndBuffer::CSndBuffer(size_t bytesize, size_t slicesize, size_t mss, size_t hea #endif { #if SRT_SNDBUF_NEW - (void)slicesize; // fake used, but not used #else // XXX decide what would be better for the implementation - allocate a single // slice, allocate all the memory in slices, or just ignore slicesize. m_iSize = int(slicesize); - (void)bytesize; + (void)pktsize; #endif m_rateEstimator.setHeaderSize(headersize); @@ -949,11 +948,17 @@ SndPktArray::Packet& SndPktArray::push() Packet& that = m_PktQueue.back(); + IF_HEAVY_LOGGING(size_t storage_before = m_Storage.storage.size()); + // Allocate the packet payload space that.m_iBusy = 0; that.m_iLength = m_Storage.blocksize; that.m_pcData = m_Storage.get(); + HLOGC(bslog.Debug, log << "SndPktArray::push: new buffer (" + << (storage_before == m_Storage.storage.size() ? "ALLOCATED" : "RECYCLED") + << "), active " << m_iCachedSize.load() << ", archived " << m_Storage.storage.size() << " buffers"); + // pushing is always treated as adding a new unique packet ++m_iNewQueued; @@ -998,6 +1003,9 @@ size_t SndPktArray::pop(size_t n) // in that case just shrink it to the existing range. m_iNewQueued = std::min(m_iNewQueued, m_PktQueue.size()); + HLOGC(bslog.Debug, log << "SndPktArray::pop: released " << n << " buffers, active " + << m_iCachedSize.load() << ", archived " << m_Storage.storage.size() << " buffers"); + // These are indexes into the m_PktQueue container, so with // removed n elements, their position in the container also // get shifted by n. diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index 646ccb0f8..5dd790695 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -227,7 +227,7 @@ struct SndPktArray public: - SndPktArray(size_t payload_len, size_t max_packets): + SndPktArray(size_t payload_len, size_t max_packets, size_t reserved): m_Storage(payload_len, max_packets), m_iCachedSize(0), m_iNewQueued(0), @@ -235,7 +235,7 @@ struct SndPktArray m_iLastRexmit(-1), m_iLossLengthCache(0) { - m_Storage.reserve(max_packets); + m_Storage.reserve(reserved); } ~SndPktArray(); @@ -405,7 +405,7 @@ class CSndBuffer // [ ----------------------------- MSS ---------------------------------------------] // [HEADER(IP-dependent)][ ................... PAYLOAD .................. ][reserved] - CSndBuffer(size_t bytesize, // size limit in bytes (will be split into packets) + CSndBuffer(size_t pktsize, // size limit in packets (of payload size) size_t slicesize, // size of the single memory chunk for payload buffers size_t mss, // value of the MSS (default: 1500, take from settings) size_t headersize, // size of the packet header (IP version dependent) diff --git a/srtcore/socketconfig.cpp b/srtcore/socketconfig.cpp index d2f880bf7..dfdbd99f0 100644 --- a/srtcore/socketconfig.cpp +++ b/srtcore/socketconfig.cpp @@ -142,7 +142,14 @@ struct CSrtConfigSetter if (bs <= 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + // THIS is when the option value is intended to limit the + // maximum size of the buffer (will use less than this if + // required for alignment) co.iSndBufSize = bs / co.bytesPerPkt(); + + // OR: it can use enough capacity to satisfy this value + // as a minimum (and use more for alignment if needed). + // co.iSndBufSize = number_slices(bs, co.bytesPerPkt()); } }; diff --git a/test/test_buffer_snd.cpp b/test/test_buffer_snd.cpp index 14b8a3639..8d966815a 100644 --- a/test/test_buffer_snd.cpp +++ b/test/test_buffer_snd.cpp @@ -151,7 +151,7 @@ class TestSndLoss: public srt::Test hvu::ofmtrefstream sout {cout}; - srt::SndPktArray packets { 1024, 20 }; + srt::SndPktArray packets { 1024, 20, 20 }; protected: From a76e5b105ff81886f04822e242ffda166baac207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 4 Dec 2025 18:26:06 +0100 Subject: [PATCH 19/26] Tracking a CI failure on Ubuntu --- test/test_buffer_rcv.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp index cd00f371c..beb100f66 100644 --- a/test/test_buffer_rcv.cpp +++ b/test/test_buffer_rcv.cpp @@ -2,6 +2,7 @@ #include #include "gtest/gtest.h" #include "buffer_rcv.h" +#include "ofmt.h" using namespace srt; using namespace std; @@ -758,8 +759,12 @@ TEST_F(CRcvBufferReadMsg, OnePacketTSBPD) { const size_t msg_pkts = 1; + hvu::ofmtrefstream sout(cout); + m_rcv_buffer->setTsbPdMode(m_tsbpd_base, false, m_delay); + sout.puts("ADD MESSAGE 1"); + const int packet_ts = 0; // Adding one message. Note that all packets have the out of order flag // set to false by default in TSBPD mode, but this flag is ignored. @@ -768,9 +773,11 @@ TEST_F(CRcvBufferReadMsg, OnePacketTSBPD) const size_t msg_bytelen = msg_pkts * m_payload_sz; array buff; + sout.puts("ADD MESSAGE 2 - expect failure"); // Confirm adding to the same location returns an error. EXPECT_EQ(addMessage(msg_pkts, 1, m_init_seqno, true, packet_ts), -1); + sout.puts("CHECK READINESS AT DELAY"); // There is one packet in the buffer, but not ready to read after delay/2 EXPECT_FALSE(m_rcv_buffer->isRcvDataReady(m_tsbpd_base + (m_delay / 2))); EXPECT_FALSE(m_rcv_buffer->isRcvDataReady(m_tsbpd_base + m_delay - sync::microseconds_from(1))); @@ -778,11 +785,18 @@ TEST_F(CRcvBufferReadMsg, OnePacketTSBPD) EXPECT_TRUE(m_rcv_buffer->isRcvDataReady(m_tsbpd_base + m_delay)); EXPECT_TRUE(m_rcv_buffer->isRcvDataReady(m_tsbpd_base + m_delay + sync::microseconds_from(1))); + sout.puts("READ a message"); + + srt_setloglevel(LOG_DEBUG); // Read out the first message const int read_len = m_rcv_buffer->readMessage(buff.data(), buff.size()); EXPECT_EQ(read_len, (int) msg_bytelen); + srt_setloglevel(LOG_ERR); + + sout.puts("Verify payload"); EXPECT_TRUE(verifyPayload(buff.data(), read_len, m_init_seqno)); + sout.puts("Verify readiness after reading"); // Check the state after a packet was read EXPECT_FALSE(m_rcv_buffer->isRcvDataReady(m_tsbpd_base + m_delay)); EXPECT_EQ(addMessage(msg_pkts, 1, m_init_seqno, false), -2); From 5d3c5be90fdc5c6495fa7e50680421e41b7855f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 8 Dec 2025 14:19:35 +0100 Subject: [PATCH 20/26] [BUG] Fixed std::shared_mutex usage for C++17. Removed external clone of std::any. --- {test => ATTIC}/any.hpp | 0 CMakeLists.txt | 13 +++++++++++ srtcore/sync.cpp | 2 +- srtcore/sync.h | 4 ++-- test/test_socket_options.cpp | 44 ++++++++++++++++++------------------ 5 files changed, 38 insertions(+), 25 deletions(-) rename {test => ATTIC}/any.hpp (100%) diff --git a/test/any.hpp b/ATTIC/any.hpp similarity index 100% rename from test/any.hpp rename to ATTIC/any.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e8407def2..16b29e1a9 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -415,6 +415,9 @@ if (DEFINED USE_CXX_STD) # so set this as global C++ standard option set (CMAKE_CXX_STANDARD ${STDCXX}) unset (FORCE_CXX_STANDARD) + if ((${STDCXX} GREATER_EQUAL 17) AND (ENABLE_STDCXX_SYNC)) + set (ENABLE_STDCXX_SHARED_MUTEX 1) + endif() # Do not set variables to not duplicate flags set (USE_CXX_STD_LIB ${STDCXX}) @@ -575,6 +578,13 @@ else() string(APPEND FEATURE_REPORT "LOGGING=DISABLED ") endif() +if (ENABLE_STDCXX_SHARED_MUTEX) + add_definitions(-DSRT_ENABLE_STDCXX_SHARED_MUTEX=1) + string(APPEND FEATURE_REPORT "SHARED_MUTEX=stdc++ ") +else() + string(APPEND FEATURE_REPORT "SHARED_MUTEX=srt ") +endif() + if (ENABLE_GETNAMEINFO) string(APPEND FEATURE_REPORT "GETNAMEINFO ") list(APPEND SRT_EXTRA_CFLAGS "-DSRT_ENABLE_GETNAMEINFO=1") @@ -1685,6 +1695,9 @@ if (ENABLE_UNITTESTS) target_include_directories(test-srt PRIVATE ${SSL_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS}) target_compile_definitions(test-srt PRIVATE "-DSRT_TEST_SYSTEM_NAME=\"${CMAKE_SYSTEM_NAME}\"") + # Exceptionally set C++17 standard for tests + srt_set_stdcxx(test-srt 17) + target_link_libraries( test-srt ${GTEST_BOTH_LIBRARIES} diff --git a/srtcore/sync.cpp b/srtcore/sync.cpp index 6ccccdca8..bbf1754df 100644 --- a/srtcore/sync.cpp +++ b/srtcore/sync.cpp @@ -289,7 +289,7 @@ int genRandomInt(int minVal, int maxVal) } #endif -#if defined(SRT_ENABLE_STDCXX_SYNC) && HAVE_CXX17 +#if defined(SRT_ENABLE_STDCXX_SHARED_MUTEX) // Shared mutex imp not required - aliased from C++17 diff --git a/srtcore/sync.h b/srtcore/sync.h index 803851c21..0e5afab5e 100644 --- a/srtcore/sync.h +++ b/srtcore/sync.h @@ -22,7 +22,7 @@ #include #include #include -#if HAVE_CXX17 +#ifdef SRT_ENABLE_STDCXX_SHARED_MUTEX #include #endif #define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_STDCXX_STEADY @@ -834,7 +834,7 @@ inline void releaseCond(Condition& cv) { cv.destroy(); } // /////////////////////////////////////////////////////////////////////////////// -#if defined(SRT_ENABLE_STDCXX_SYNC) && HAVE_CXX17 +#if defined(SRT_ENABLE_STDCXX_SHARED_MUTEX) using SharedMutex SRT_TSA_CAPABILITY("mutex") = std::shared_mutex; #else diff --git a/test/test_socket_options.cpp b/test/test_socket_options.cpp index 8838ec9e5..5ce0fcc7f 100644 --- a/test/test_socket_options.cpp +++ b/test/test_socket_options.cpp @@ -10,6 +10,7 @@ * Haivision Systems Inc. */ +#include #include #include #include @@ -18,7 +19,6 @@ #include "test_env.h" // SRT includes -#include "any.hpp" #include "socketconfig.h" #include "srt.h" @@ -158,9 +158,9 @@ class TestGroupOptions: public TestOptionsCommon #endif #if PLEASE_LOG -static std::string to_string(const linb::any& val) +static std::string to_string(const std::any& val) { - using namespace linb; + using namespace std; if (val.type() == typeid(const char*)) { @@ -274,11 +274,11 @@ struct OptionTestEntry const char* optname; // TODO: move to a separate array, function or std::map. RestrictionType restriction; // TODO: consider using SRTO_R_PREBIND, etc. from core.cpp size_t opt_len; - linb::any min_val; - linb::any max_val; - linb::any dflt_val; - linb::any ndflt_val; - vector invalid_vals; + std::any min_val; + std::any max_val; + std::any dflt_val; + std::any ndflt_val; + vector invalid_vals; Flags::type flags; bool allof() const { return true; } @@ -437,10 +437,10 @@ bool CheckDefaultValue(const OptionTestEntry& entry, SRTSOCKET sock, const char* { LOGD(cerr << "Will check default value: " << entry.optname << " = " << to_string(entry.dflt_val) << ": " << desc << endl); try { - const ValueType dflt_val = linb::any_cast(entry.dflt_val); + const ValueType dflt_val = std::any_cast(entry.dflt_val); CheckGetSockOpt(entry, sock, dflt_val, desc); } - catch (const linb::bad_any_cast&) + catch (const std::bad_any_cast&) { std::cerr << entry.optname << " default value type: " << entry.dflt_val.type().name() << "\n"; return false; @@ -453,16 +453,16 @@ template bool CheckSetNonDefaultValue(const OptionTestEntry& entry, SRTSOCKET sock, int expected_return, const char* desc) { try { - /*const ValueType dflt_val = linb::any_cast(entry.dflt_val); - const ValueType min_val = linb::any_cast(entry.min_val); - const ValueType max_val = linb::any_cast(entry.max_val);*/ + /*const ValueType dflt_val = std::any_cast(entry.dflt_val); + const ValueType min_val = std::any_cast(entry.min_val); + const ValueType max_val = std::any_cast(entry.max_val);*/ //const ValueType ndflt_val = (min_val != dflt_val) ? min_val : max_val; - const ValueType ndflt_val = linb::any_cast(entry.ndflt_val);; + const ValueType ndflt_val = std::any_cast(entry.ndflt_val);; CheckSetSockOpt(entry, sock, ndflt_val, expected_return, desc); } - catch (const linb::bad_any_cast&) + catch (const std::bad_any_cast&) { std::cerr << entry.optname << " non-default value type: " << entry.ndflt_val.type().name() << "\n"; return false; @@ -475,13 +475,13 @@ template bool CheckMinValue(const OptionTestEntry& entry, SRTSOCKET sock, const char* desc) { try { - const ValueType min_val = linb::any_cast(entry.min_val); + const ValueType min_val = std::any_cast(entry.min_val); CheckSetSockOpt(entry, sock, min_val, SRT_SUCCESS, desc); - const ValueType dflt_val = linb::any_cast(entry.dflt_val); + const ValueType dflt_val = std::any_cast(entry.dflt_val); CheckSetSockOpt(entry, sock, dflt_val, SRT_SUCCESS, desc); } - catch (const linb::bad_any_cast&) + catch (const std::bad_any_cast&) { std::cerr << entry.optname << " min value type: " << entry.min_val.type().name() << "\n"; return false; @@ -494,10 +494,10 @@ template bool CheckMaxValue(const OptionTestEntry& entry, SRTSOCKET sock, const char* desc) { try { - const ValueType max_val = linb::any_cast(entry.max_val); + const ValueType max_val = std::any_cast(entry.max_val); CheckSetSockOpt(entry, sock, max_val, SRT_SUCCESS, desc); } - catch (const linb::bad_any_cast&) + catch (const std::bad_any_cast&) { std::cerr << entry.optname << " max value type: " << entry.max_val.type().name() << "\n"; return false; @@ -513,10 +513,10 @@ bool CheckInvalidValues(const OptionTestEntry& entry, SRTSOCKET sock, const char { LOGD(cerr << "Will check INVALID value: " << entry.optname << " : " << to_string(inval) << ": " << sock_name << endl); try { - const ValueType val = linb::any_cast(inval); + const ValueType val = std::any_cast(inval); CheckSetSockOpt(entry, sock, val, SRT_ERROR, sock_name); } - catch (const linb::bad_any_cast&) + catch (const std::bad_any_cast&) { std::cerr << entry.optname << " value type: " << inval.type().name() << "\n"; return false; From 0144e17d50d5e8538248ca25e18ed744f7114c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 8 Dec 2025 14:36:28 +0100 Subject: [PATCH 21/26] Logging: no passing string by value. Removed verbose entries for RcvBuffer test --- logging/logging.cpp | 2 +- logging/logging.h | 2 +- test/test_buffer_rcv.cpp | 12 ------------ 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/logging/logging.cpp b/logging/logging.cpp index ba60fa853..884759a1f 100644 --- a/logging/logging.cpp +++ b/logging/logging.cpp @@ -294,7 +294,7 @@ LogDispatcher::Proxy::Proxy(LogDispatcher& guy) } } -LogDispatcher::Proxy::Proxy(LogDispatcher& guy, const char* f, int l, std::string a) +LogDispatcher::Proxy::Proxy(LogDispatcher& guy, const char* f, int l, const std::string& a) : that(guy) , i_file(f) , i_line(l) diff --git a/logging/logging.h b/logging/logging.h index 53378a962..926f67a5c 100644 --- a/logging/logging.h +++ b/logging/logging.h @@ -232,7 +232,7 @@ class LogDispatcher std::string ExtractName(std::string pretty_function); Proxy(LogDispatcher& guy); - Proxy(LogDispatcher& guy, const char* f, int l, std::string a); + Proxy(LogDispatcher& guy, const char* f, int l, const std::string& a); // Copy constructor is needed due to noncopyable ostringstream. // This is used only in creation of the default object, so just diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp index beb100f66..065ffdd52 100644 --- a/test/test_buffer_rcv.cpp +++ b/test/test_buffer_rcv.cpp @@ -759,12 +759,8 @@ TEST_F(CRcvBufferReadMsg, OnePacketTSBPD) { const size_t msg_pkts = 1; - hvu::ofmtrefstream sout(cout); - m_rcv_buffer->setTsbPdMode(m_tsbpd_base, false, m_delay); - sout.puts("ADD MESSAGE 1"); - const int packet_ts = 0; // Adding one message. Note that all packets have the out of order flag // set to false by default in TSBPD mode, but this flag is ignored. @@ -773,11 +769,9 @@ TEST_F(CRcvBufferReadMsg, OnePacketTSBPD) const size_t msg_bytelen = msg_pkts * m_payload_sz; array buff; - sout.puts("ADD MESSAGE 2 - expect failure"); // Confirm adding to the same location returns an error. EXPECT_EQ(addMessage(msg_pkts, 1, m_init_seqno, true, packet_ts), -1); - sout.puts("CHECK READINESS AT DELAY"); // There is one packet in the buffer, but not ready to read after delay/2 EXPECT_FALSE(m_rcv_buffer->isRcvDataReady(m_tsbpd_base + (m_delay / 2))); EXPECT_FALSE(m_rcv_buffer->isRcvDataReady(m_tsbpd_base + m_delay - sync::microseconds_from(1))); @@ -785,18 +779,12 @@ TEST_F(CRcvBufferReadMsg, OnePacketTSBPD) EXPECT_TRUE(m_rcv_buffer->isRcvDataReady(m_tsbpd_base + m_delay)); EXPECT_TRUE(m_rcv_buffer->isRcvDataReady(m_tsbpd_base + m_delay + sync::microseconds_from(1))); - sout.puts("READ a message"); - - srt_setloglevel(LOG_DEBUG); // Read out the first message const int read_len = m_rcv_buffer->readMessage(buff.data(), buff.size()); EXPECT_EQ(read_len, (int) msg_bytelen); - srt_setloglevel(LOG_ERR); - sout.puts("Verify payload"); EXPECT_TRUE(verifyPayload(buff.data(), read_len, m_init_seqno)); - sout.puts("Verify readiness after reading"); // Check the state after a packet was read EXPECT_FALSE(m_rcv_buffer->isRcvDataReady(m_tsbpd_base + m_delay)); EXPECT_EQ(addMessage(msg_pkts, 1, m_init_seqno, false), -2); From 3455f5c9c5d1d74763471e60581ef0e7717769fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Wed, 10 Dec 2025 17:01:30 +0100 Subject: [PATCH 22/26] Unified rexmit extraction. Removed old implementation --- .travis.yml | 10 +- srtcore/buffer_snd.cpp | 968 +++++---------------------------------- srtcore/buffer_snd.h | 99 ++-- srtcore/core.cpp | 183 ++------ srtcore/core.h | 5 +- test/test_buffer_snd.cpp | 54 +++ 6 files changed, 275 insertions(+), 1044 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5c54148ea..ee6a2ceab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ # - COLON characters (in freestanding strings) not allowed in the script language: cpp -dist: xenial +dist: trusty addons: apt: @@ -32,6 +32,11 @@ jobs: - env: - BUILD_TYPE=Debug CFG="nologging mbedtls monotonic werror" - CMAKE_OPTS='-DENABLE_LOGGING=OFF -DUSE_ENCLIB=mbedtls -DENABLE_MONOTONIC_CLOCK=ON -DENABLE_BONDING=ON -DCMAKE_CXX_FLAGS="-Werror"' + - os: linux + dist: xenial + env: + - BUILD_TYPE=Release CFG="old-distro notests werror" + - CMAKE_OPTS='-DUSE_CXX_STD=C++98 -DCMAKE_CXX_FLAGS="-Werror"' - os: linux env: BUILD_TYPE=Release CFG=default # - os: osx @@ -60,7 +65,7 @@ jobs: - ./Configure --cross-compile-prefix=x86_64-w64-mingw32- mingw64 - make - cd .. - env: BUILD_TYPE=Release CFG=no-UT + env: BUILD_TYPE=Release CFG=notests # Power jobs # Forcing Focal distro because Xenial @@ -84,6 +89,7 @@ script: exit 1; fi; - export REQUIRE_UNITTESTS=1 + - if [[ $CFG == *"notests"* ]]; then REQUIRE_UNITTESTS=0; fi - if [ "$TRAVIS_COMPILER" == "x86_64-w64-mingw32-g++" ]; then CMAKE_OPTS+=" -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc"; CMAKE_OPTS+=" -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++"; diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 4bb6abd8a..541fd9421 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -74,41 +74,21 @@ CSndBuffer::CSndBuffer(size_t pktsize, size_t slicesize, size_t mss, size_t head m_iSndUpdateAck(SRT_SEQNO_NONE), m_iNextMsgNo(1), m_iBytesCount(0), -#if SRT_SNDBUF_NEW m_iCapacity(pktsize), // To avoid performance degradation during the transmission, // we allocate in advance all required blocks so that they are // picked up from the storage when required. m_Packets(m_iBlockLen, m_iCapacity, slicesize) -#else - m_SndLossList(flwsize * 2), - m_pBlock(NULL), - m_pFirstBlock(NULL), - m_pCurrBlock(NULL), - m_pLastBlock(NULL), - m_pFirstMemSlice(NULL), - m_iCount(0) -#endif { -#if SRT_SNDBUF_NEW -#else - // XXX decide what would be better for the implementation - allocate a single - // slice, allocate all the memory in slices, or just ignore slicesize. - m_iSize = int(slicesize); - (void)pktsize; -#endif - m_rateEstimator.setHeaderSize(headersize); initialize(); setupMutex(m_BufLock, "SndBuf"); } -#if SRT_SNDBUF_NEW - void CSndBuffer::initialize() { - // Here we can decide, how eagerly the memory can be allocated. + // If any further initialization is needed } void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) @@ -356,7 +336,8 @@ int32_t CSndBuffer::getMsgNoAtSeq(const int32_t seqno) return m_Packets[offset].getMsgSeq(); } -int CSndBuffer::readOldPacket(int32_t seqno, CSndPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) +// XXX Likely unused (left for the use by tests) +int CSndBuffer::readOldPacket(int32_t seqno, CSndPacket& w_sndpkt, steady_clock::time_point& w_srctime, DropRange& w_drop) { ScopedLock bufferguard(m_BufLock); @@ -371,6 +352,14 @@ int CSndBuffer::readOldPacket(int32_t seqno, CSndPacket& w_packet, steady_clock: // Unlike receiver buffer, in the sender buffer packets are always stored // one after another and there are no gaps. Checking the valid range of offset // suffices to grant existence of a packet. + + w_sndpkt.pkt.set_seqno(seqno); + + return readPacketInternal(offset, (w_sndpkt), (w_srctime), (w_drop)); +} + +int CSndBuffer::readPacketInternal(int offset, CSndPacket& w_sndpkt, steady_clock::time_point& w_srctime, DropRange& w_drop) +{ SndPktArray::Packet* p = &m_Packets[offset]; #if HVU_ENABLE_HEAVY_LOGGING @@ -378,11 +367,9 @@ int CSndBuffer::readOldPacket(int32_t seqno, CSndPacket& w_packet, steady_clock: int32_t last_seq = p->m_iSeqNo; #endif - w_packet.pkt.set_seqno(seqno); - // This is rexmit request, so the packet should have the sequence number // already set when it was once sent uniquely. - SRT_ASSERT(p->m_iSeqNo == w_packet.pkt.seqno()); + SRT_ASSERT(p->m_iSeqNo == w_sndpkt.pkt.seqno()); // Check if the block that is the next candidate to send (m_pCurrBlock pointing) is stale. @@ -417,8 +404,8 @@ int CSndBuffer::readOldPacket(int32_t seqno, CSndPacket& w_packet, steady_clock: m_Packets.set_expired(lastx); w_drop.msgno = p->getMsgSeq(); - w_drop.seqno[DropRange::BEGIN] = w_packet.pkt.seqno(); - w_drop.seqno[DropRange::END] = CSeqNo::incseq(w_packet.pkt.seqno(), lastx - offset); + w_drop.seqno[DropRange::BEGIN] = w_sndpkt.pkt.seqno(); + w_drop.seqno[DropRange::END] = CSeqNo::incseq(w_sndpkt.pkt.seqno(), lastx - offset); // We let the caller handle it, while we state no packet delivered. // NOTE: the expiration of a message doesn't imply recovation from the buffer. @@ -426,14 +413,14 @@ int CSndBuffer::readOldPacket(int32_t seqno, CSndPacket& w_packet, steady_clock: return READ_DROP; } - w_packet.pkt.m_pcData = p->m_pcData; + w_sndpkt.pkt.m_pcData = p->m_pcData; const int readlen = p->m_iLength; - w_packet.pkt.setLength(readlen, m_iBlockLen); + w_sndpkt.pkt.setLength(readlen, m_iBlockLen); // We state that the requested seqno refers to a historical (not unique) // packet, hence the encryption action has encrypted the data and updated // the flags. - w_packet.pkt.set_msgflags(p->m_iMsgNoBitset); + w_sndpkt.pkt.set_msgflags(p->m_iMsgNoBitset); w_srctime = p->m_tsOriginTime; // This function is called when packet retransmission is triggered. @@ -441,10 +428,10 @@ int CSndBuffer::readOldPacket(int32_t seqno, CSndPacket& w_packet, steady_clock: p->m_tsRexmitTime = steady_clock::now(); ++p->m_iBusy; - w_packet.acquire_busy(p->m_iSeqNo, this); + w_sndpkt.acquire_busy(p->m_iSeqNo, this); HLOGC(bslog.Debug, - log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.pkt.seqno() + log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_sndpkt.pkt.seqno() << " size=" << readlen << " to send [REXMIT]"); return readlen; @@ -461,6 +448,81 @@ sync::steady_clock::time_point CSndBuffer::getRexmitTime(const int32_t seqno) return m_Packets[offset].m_tsRexmitTime; } +int CSndBuffer::extractFirstRexmitPacket(const duration& min_rexmit_interval, int32_t& w_current_seqno, CSndPacket& w_sndpkt, + sync::steady_clock::time_point& w_tsOrigin, std::vector& w_drops) +{ + // Get the first sequence for retransmission, bypassing and taking care of + // those that are in the forgotten region, as well as required to be rejected. + // Look into the loss list and drop all sequences that are alrady revoked + // from the sender buffer, send the drop request if needed, and return the + // sender buffer offset for the next packet to retransmit, or -1 if there + // is no retransmission candidate at the moment. + + ScopedLock bufferguard(m_BufLock); + + int seq = SRT_SEQNO_NONE; + + int offset = -1; + int payload = 0; // default: no packet extracted + + HLOGC(qslog.Debug, log << "REXMIT: looking for loss report since %" << m_iSndLastDataAck << "..."); + + // REPEATABLE BLOCK (not a real loop) + // The call to readPacketInternal may result in a drop request, which must be + // handled and then the call repeated, until it returns a valid packet + // or no packet to retransmit. + for (;;) + { + // This is preferably done only once; exceptionally it may be + // repeated if it turns out that the message has expired + // (a feature used exclusively in message-mode). + offset = m_Packets.extractFirstLoss(min_rexmit_interval); + + // No loss found - return 0: no lost packets extracted. + if (offset == -1) + { + HLOGC(qslog.Debug, log << "REXMIT: no loss found"); + break; + } + + seq = CSeqNo::incseq(m_iSndLastDataAck, offset); + + HLOGC(qslog.Debug, log << "REXMIT: got %" << seq << ", requesting that packet from sndbuf with first %" + << firstSeqNo()); + + // Extract the packet from the sender buffer that is mapped to the expected sequence + // number, bypassing and taking care of those that are decided to be dropped. + + typedef CSndBuffer::DropRange DropRange; + DropRange buffer_drop; + + w_sndpkt.pkt.set_seqno(seq); + + // Might be that if you read THIS packet, it results in a drop request. + // BUT: if you got drop request for this 'offset' (sequence effectively), then + // you won't get this sequence again. Forget this then and pick up the next loss candidate. + payload = readPacketInternal(offset, (w_sndpkt), (w_tsOrigin), (buffer_drop)); + if (payload == CSndBuffer::READ_DROP) + { + SRT_ASSERT(CSeqNo::seqoff(buffer_drop.seqno[DropRange::BEGIN], buffer_drop.seqno[DropRange::END]) >= 0); + + HLOGC(qslog.Debug, + log << "... loss-reported packets expired in SndBuf - requesting DROP: #" + << buffer_drop.msgno << " %(" << buffer_drop.seqno[DropRange::BEGIN] << " - " + << buffer_drop.seqno[DropRange::END] << ")"); + w_drops.push_back(buffer_drop); + + // skip all dropped packets + w_current_seqno = CSeqNo::maxseq(w_current_seqno, buffer_drop.seqno[DropRange::END]); + continue; + } + + break; + } + + return payload; +} + void CSndBuffer::releasePacket(int32_t seqno) { ScopedLock bufferguard(m_BufLock); @@ -546,6 +608,7 @@ bool CSndBuffer::cancelLostSeq(int32_t seq) return m_Packets.clear_loss(offset); } +// XXX likely unused int32_t CSndBuffer::popLostSeq(DropRange& w_drop) { static const DropRange nodrop = { {SRT_SEQNO_NONE, SRT_SEQNO_NONE}, SRT_MSGNO_CONTROL }; @@ -1097,7 +1160,7 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne m_iFirstRexmit = m_iLastRexmit = offset_lo; m_iLossLengthCache = loss_length; - set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + update_next_rexmit_time(offset_lo, offset_hi, next_rexmit_time); HLOGC(bslog.Debug, log << "insert_loss: 1&1 record: " << offset_lo << "..." << offset_hi << " (" << loss_length << " cells)"); @@ -1184,7 +1247,7 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne setupNode(offset_lo, offset_hi); } - set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + update_next_rexmit_time(offset_lo, offset_hi, next_rexmit_time); string msg SRT_ATR_UNUSED; SRT_ASSERT(validateLossIntegrity((msg))); @@ -1243,7 +1306,7 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne m_PktQueue[before_node_index].m_iNextLossGroupOffset = offset_lo - before_node_index; - set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + update_next_rexmit_time(offset_lo, offset_hi, next_rexmit_time); string msg SRT_ATR_UNUSED; SRT_ASSERT(validateLossIntegrity((msg))); @@ -1275,7 +1338,7 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne // Just update the time for the requested range, but do nothing else. // The inserted records completely overlap with the existing ones, // so no changes are necessary, except updating the retransmission time. - set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + update_next_rexmit_time(offset_lo, offset_hi, next_rexmit_time); return true; } @@ -1359,7 +1422,7 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne // Set the rexmit time only to the range that was requested to be inserted, // even if this is effectively a fragment of a record. - set_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + update_next_rexmit_time(offset_lo, offset_hi, next_rexmit_time); string msg SRT_ATR_UNUSED; SRT_ASSERT(validateLossIntegrity((msg))); return true; @@ -1542,7 +1605,7 @@ bool SndPktArray::validateLossIntegrity(std::string& w_message) } -int SndPktArray::extractFirstLoss() +int SndPktArray::extractFirstLoss(const duration& miniv) { // No loss at all if (m_iFirstRexmit == -1) @@ -1561,6 +1624,8 @@ int SndPktArray::extractFirstLoss() time_point now = steady_clock::now(); + int last_cleared = -1; + // Walk over the container to find the valid loss sequence for (int loss_begin = m_iFirstRexmit; loss_begin != -1; loss_begin = next_loss(loss_begin)) { @@ -1572,27 +1637,48 @@ int SndPktArray::extractFirstLoss() if (!is_zero(p.m_tsNextRexmitTime)) { // Ok, so this cell will be taken, but it might be the future. - if (p.m_tsNextRexmitTime > now) + if (!p.updated_rexmit_time_passed(now, miniv)) { if (stop_revoke == -1 && i > 0) stop_revoke = i - 1; + HLOGC(qslog.Debug, log << "... skipped +" << i << " - too early by " + << FormatDurationAuto(now + miniv - p.m_tsNextRexmitTime)); continue; } - // Clear that packet from being rexmit-eligible. p.m_tsNextRexmitTime = time_point(); if (stop_revoke == -1) + { + HLOGC(qslog.Debug, log << "... FOUND +" << i << " - removing up to this one"); remove_loss(i); // Remove all previous loss records, including this one + } else + { + HLOGC(qslog.Debug, log << "... FOUND +" << i << " - removing up to +" << stop_revoke); remove_loss(stop_revoke); + } return i; } + else + { + HLOGC(qslog.Debug, log << "... skipped +" << i << " - cleared earlier"); + // This will be done while the loop is searching + // for the first FILLED record. When hit the first + // filled record, it will be reported, and all losses + // up to this one will be cleared. This one is required + // for a case when occasionally all existing loss entries + // were selectively cleared. + last_cleared = i; + } // If it was cleared, continue searching. } } + if (last_cleared != -1) + remove_loss(last_cleared); + return -1; } @@ -1685,807 +1771,5 @@ void SndPktArray::showline(int index, PacketShowState& st, hvu::ofmtbufstream& o } } -#else - -void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) -{ - int32_t& w_msgno = w_mctrl.msgno; - int32_t& w_seqno = w_mctrl.pktseq; - int64_t& w_srctime = w_mctrl.srctime; - const int& ttl = w_mctrl.msgttl; - const int iPktLen = getMaxPacketLen(); - const int iNumBlocks = number_slices(len, iPktLen); - - if (m_iSndLastDataAck == SRT_SEQNO_NONE) - m_iSndLastDataAck = w_seqno; - - HLOGC(bslog.Debug, - log << "addBuffer: needs=" << iNumBlocks << " buffers for " << len << " bytes. Taken=" - << m_iCount.load() << "/" << m_iSize); - // Retrieve current time before locking the mutex to be closer to packet submission event. - const steady_clock::time_point tnow = steady_clock::now(); - - ScopedLock bufferguard(m_BufLock); - // Dynamically increase sender buffer if there is not enough room. - while (iNumBlocks + m_iCount >= m_iSize) - { - HLOGC(bslog.Debug, log << "addBuffer: ... still lacking " << (iNumBlocks + m_iCount - m_iSize) << " buffers..."); - increase(); - } - - const int32_t inorder = w_mctrl.inorder ? MSGNO_PACKET_INORDER::mask : 0; - HLOGC(bslog.Debug, - log << CONID() << "addBuffer: adding " << iNumBlocks << " packets (" << len << " bytes) to send, msgno=" - << (w_msgno > 0 ? w_msgno : m_iNextMsgNo) << (inorder ? "" : " NOT") << " in order"); - - // Calculate origin time (same for all blocks of the message). - m_tsLastOriginTime = w_srctime ? time_point() + microseconds_from(w_srctime) : tnow; - // Rewrite back the actual value, even if it stays the same, so that the calling facilities can reuse it. - // May also be a subject to conversion error, thus the actual value is signalled back. - w_srctime = count_microseconds(m_tsLastOriginTime.time_since_epoch()); - - // The sequence number passed to this function is the sequence number - // that the very first packet from the packet series should get here. - // If there's more than one packet, this function must increase it by itself - // and then return the accordingly modified sequence number in the reference. - - Block* s = m_pLastBlock; - - if (w_msgno == SRT_MSGNO_NONE) // DEFAULT-UNCHANGED msgno supplied - { - HLOGC(bslog.Debug, log << "addBuffer: using internally managed msgno=" << m_iNextMsgNo); - w_msgno = m_iNextMsgNo; - } - else - { - HLOGC(bslog.Debug, log << "addBuffer: OVERWRITTEN by msgno supplied by caller: msgno=" << w_msgno); - m_iNextMsgNo = w_msgno; - } - - for (int i = 0; i < iNumBlocks; ++i) - { - int pktlen = len - i * iPktLen; - if (pktlen > iPktLen) - pktlen = iPktLen; - - HLOGC(bslog.Debug, - log << "addBuffer: %" << w_seqno << " #" << w_msgno << " offset=" << (i * iPktLen) - << " size=" << pktlen << " TO BUFFER:" << (void*)s->m_pcData); - memcpy((s->m_pcData), data + i * iPktLen, pktlen); - s->m_iLength = pktlen; - - s->m_iSeqNo = w_seqno; - w_seqno = CSeqNo::incseq(w_seqno); - - s->m_iMsgNoBitset = m_iNextMsgNo | inorder; - if (i == 0) - s->m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST); - if (i == iNumBlocks - 1) - s->m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST); - // NOTE: if i is neither 0 nor size-1, it resuls with PB_SUBSEQUENT. - // if i == 0 == size-1, it results with PB_SOLO. - // Packets assigned to one message can be: - // [PB_FIRST] [PB_SUBSEQUENT] [PB_SUBSEQUENT] [PB_LAST] - 4 packets per message - // [PB_FIRST] [PB_LAST] - 2 packets per message - // [PB_SOLO] - 1 packet per message - - s->m_iTTL = ttl; - s->m_tsRexmitTime = time_point(); - s->m_tsOriginTime = m_tsLastOriginTime; - - // Should never happen, as the call to increase() should ensure enough buffers. - SRT_ASSERT(s->m_pNext); - s = s->m_pNext; - } - m_pLastBlock = s; - - m_iCount = m_iCount + iNumBlocks; - m_iBytesCount += len; - - m_rateEstimator.updateInputRate(m_tsLastOriginTime, iNumBlocks, len); - updAvgBufSize(m_tsLastOriginTime); - - // MSGNO_SEQ::mask has a form: 00000011111111... - // At least it's known that it's from some index inside til the end (to bit 0). - // If this value has been reached in a step of incrementation, it means that the - // maximum value has been reached. Casting to int32_t to ensure the same sign - // in comparison, although it's far from reaching the sign bit. - - const int nextmsgno = ++MsgNo(m_iNextMsgNo); - HLOGC(bslog.Debug, log << "CSndBuffer::addBuffer: updating msgno: #" << m_iNextMsgNo << " -> #" << nextmsgno); - m_iNextMsgNo = nextmsgno; -} - -int CSndBuffer::addBufferFromFile(fstream& ifs, int len) -{ - const int iPktLen = getMaxPacketLen(); - const int iNumBlocks = number_slices(len, iPktLen); - - HLOGC(bslog.Debug, - log << "addBufferFromFile: size=" << m_iCount.load() << " reserved=" << m_iSize << " needs=" << iPktLen - << " buffers for " << len << " bytes, msg #" << m_iNextMsgNo); - - // dynamically increase sender buffer - while (iNumBlocks + m_iCount >= m_iSize) - { - HLOGC(bslog.Debug, - log << "addBufferFromFile: ... still lacking " << (iNumBlocks + m_iCount - m_iSize) << " buffers..."); - increase(); - } - - Block* s = m_pLastBlock; - int total = 0; - for (int i = 0; i < iNumBlocks; ++i) - { - if (ifs.bad() || ifs.fail() || ifs.eof()) - break; - - int pktlen = len - i * iPktLen; - if (pktlen > iPktLen) - pktlen = iPktLen; - - HLOGC(bslog.Debug, - log << "addBufferFromFile: reading from=" << (i * iPktLen) << " size=" << pktlen - << " TO BUFFER:" << (void*)s->m_pcData); - ifs.read(s->m_pcData, pktlen); - if ((pktlen = int(ifs.gcount())) <= 0) - break; - - // currently file transfer is only available in streaming mode, message is always in order, ttl = infinite - s->m_iMsgNoBitset = m_iNextMsgNo | MSGNO_PACKET_INORDER::mask; - if (i == 0) - s->m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST); - if (i == iNumBlocks - 1) - s->m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST); - // NOTE: PB_FIRST | PB_LAST == PB_SOLO. - // none of PB_FIRST & PB_LAST == PB_SUBSEQUENT. - - s->m_iLength = pktlen; - s->m_iTTL = SRT_MSGTTL_INF; - s = s->m_pNext; - - total += pktlen; - } - m_pLastBlock = s; - - enterCS(m_BufLock); - m_iCount = m_iCount + iNumBlocks; - m_iBytesCount += total; - - leaveCS(m_BufLock); - - m_iNextMsgNo++; - if (m_iNextMsgNo == int32_t(MSGNO_SEQ::mask)) - m_iNextMsgNo = 1; - - return total; -} - -int CSndBuffer::extractUniquePacket(CSndPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc) -{ - int readlen = 0; - w_seqnoinc = 0; - - ScopedLock bufferguard(m_BufLock); - while (m_pCurrBlock != m_pLastBlock) - { - // Make the packet REFLECT the data stored in the buffer. - w_packet.pkt.m_pcData = m_pCurrBlock->m_pcData; - readlen = m_pCurrBlock->m_iLength; - w_packet.pkt.setLength(readlen, m_iBlockLen); - w_packet.pkt.set_seqno(m_pCurrBlock->m_iSeqNo); - - // 1. On submission (addBuffer), the KK flag is set to EK_NOENC (0). - // 2. The extractUniquePacket() is called to get the original (unique) payload not ever sent yet. - // The payload must be encrypted for the first time if the encryption - // is enabled (arg kflgs != EK_NOENC). The KK encryption flag of the data packet - // header must be set and remembered accordingly (see EncryptionKeySpec). - // 3. The next time this packet is read (only for retransmission), the payload is already - // encrypted, and the proper flag value is already stored. - - // TODO: Alternatively, encryption could happen before the packet is submitted to the buffer - // (before the addBuffer() call), and corresponding flags could be set accordingly. - // This may also put an encryption burden on the application thread, rather than the sending thread, - // which could be more efficient. Note that packet sequence number must be properly set in that case, - // as it is used as a counter for the AES encryption. - if (kflgs == -1) - { - HLOGC(bslog.Debug, log << CONID() << " CSndBuffer: ERROR: encryption required and not possible. NOT SENDING."); - readlen = 0; - } - else - { - m_pCurrBlock->m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(kflgs); - } - - Block* p = m_pCurrBlock; - w_packet.pkt.set_msgflags(m_pCurrBlock->m_iMsgNoBitset); - w_srctime = m_pCurrBlock->m_tsOriginTime; - m_pCurrBlock = m_pCurrBlock->m_pNext; - - if ((p->m_iTTL >= 0) && (count_milliseconds(steady_clock::now() - w_srctime) > p->m_iTTL)) - { - LOGC(bslog.Warn, log << CONID() << "CSndBuffer: skipping packet %" << p->m_iSeqNo << " #" << p->getMsgSeq() << " with TTL=" << p->m_iTTL); - // Skip this packet due to TTL expiry. - readlen = 0; - ++w_seqnoinc; - continue; - } - - w_packet.seqno = w_packet.pkt.seqno(); - w_packet.srcbuf = this; - - HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: picked up packet to send: size=" << readlen - << " #" << w_packet.pkt.getMsgSeq() - << " %" << w_packet.pkt.seqno() - << " !" << BufferStamp(w_packet.pkt.m_pcData, w_packet.pkt.getLength())); - - break; - } - - return readlen; -} - -void CSndBuffer::releasePacket(int32_t /*seqno*/) -{ - // only debug -} - -CSndBuffer::time_point CSndBuffer::peekNextOriginal() const -{ - ScopedLock bufferguard(m_BufLock); - if (m_pCurrBlock == m_pLastBlock) - return time_point(); - - return m_pCurrBlock->m_tsOriginTime; -} - -int32_t CSndBuffer::getMsgNoAtSeq(const int32_t seqno) -{ - ScopedLock bufferguard(m_BufLock); - - int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); - - if (offset < 0 || offset >= m_iCount) - { - // Prevent accessing the last "marker" block - LOGC(bslog.Error, - log << "CSndBuffer::getMsgNoAtSeq: IPE: for %" << seqno << " offset=" << offset << " outside container; max offset=" << m_iCount.load()); - return SRT_MSGNO_CONTROL; - } - - Block* p = m_pFirstBlock; - if (p) - { - HLOGC(bslog.Debug, - log << "CSndBuffer::getMsgNoAtSeq: FIRST MSG: size=" << p->m_iLength << " %" << p->m_iSeqNo << " #" - << p->getMsgSeq() << " !" << BufferStamp(p->m_pcData, p->m_iLength)); - } - - // XXX Suboptimal procedure to keep the blocks identifiable - // by sequence number. Consider using some circular buffer. - int i; - Block* ee SRT_ATR_UNUSED = 0; - for (i = 0; i < offset && p; ++i) - { - ee = p; - p = p->m_pNext; - } - - if (!p) - { - LOGC(bslog.Error, - log << "CSndBuffer::getMsgNoAt: IPE: for %" << seqno << "offset=" << offset << " not found, stopped at " << i << " with #" - << (ee ? ee->getMsgSeq() : SRT_MSGNO_NONE)); - return SRT_MSGNO_CONTROL; - } - - HLOGC(bslog.Debug, - log << "CSndBuffer::getMsgNoAt: for %" << seqno << " offset=" << offset << " found, size=" << p->m_iLength << " %" << p->m_iSeqNo - << " #" << p->getMsgSeq() << " !" << BufferStamp(p->m_pcData, p->m_iLength)); - - return p->getMsgSeq(); -} - -int CSndBuffer::readOldPacket(int32_t seqno, CSndPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) -{ - ScopedLock bufferguard(m_BufLock); - - int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); - - Block* p = m_pFirstBlock; - - // XXX Suboptimal procedure to keep the blocks identifiable - // by sequence number. Consider using some circular buffer. - for (int i = 0; i < offset && p != m_pLastBlock; ++i) - { - p = p->m_pNext; - } - if (p == m_pLastBlock) - { - LOGC(bslog.Error, log << "CSndBuffer::extractUniquePacket: offset " << offset << " too large!"); - return READ_NONE; - } -#if HVU_ENABLE_HEAVY_LOGGING - const int32_t first_seq = p->m_iSeqNo; - int32_t last_seq = p->m_iSeqNo; -#endif - - w_packet.pkt.set_seqno(seqno); - - // This is rexmit request, so the packet should have the sequence number - // already set when it was once sent uniquely. - SRT_ASSERT(p->m_iSeqNo == w_packet.pkt.seqno()); - - // Check if the block that is the next candidate to send (m_pCurrBlock pointing) is stale. - - // If so, then inform the caller that it should first take care of the whole - // message (all blocks with that message id). Shift the m_pCurrBlock pointer - // to the position past the last of them. Then return -1 and set the - // msgno bitset packet field to the message id that should be dropped as - // a whole. - - // After taking care of that, the caller should immediately call this function again, - // this time possibly in order to find the real data to be sent. - - // if found block is stale - // (This is for messages that have declared TTL - messages that fail to be sent - // before the TTL defined time comes, will be dropped). - - if ((p->m_iTTL >= 0) && (count_milliseconds(steady_clock::now() - p->m_tsOriginTime) > p->m_iTTL)) - { - w_drop.msgno = p->getMsgSeq(); - int msglen = 1; - p = p->m_pNext; - bool move = false; - while (p != m_pLastBlock && w_drop.msgno == p->getMsgSeq()) - { -#if HVU_ENABLE_HEAVY_LOGGING - last_seq = p->m_iSeqNo; -#endif - if (p == m_pCurrBlock) - move = true; - p = p->m_pNext; - if (move) - m_pCurrBlock = p; - msglen++; - } - - HLOGC(bslog.Debug, - log << "CSndBuffer::extractUniquePacket: due to TTL exceeded, %(" << first_seq << " - " << last_seq << "), " - << msglen << " packets to drop with #" << w_drop.msgno); - - // Theoretically as the seq numbers are being tracked, you should be able - // to simply take the sequence number from the block. But this is a new - // feature and should be only used after refax for the sender buffer to - // make it manage the sequence numbers inside, instead of by CUDT::m_iSndLastDataAck. - w_drop.seqno[DropRange::BEGIN] = w_packet.pkt.seqno(); - w_drop.seqno[DropRange::END] = CSeqNo::incseq(w_packet.pkt.seqno(), msglen - 1); - - m_SndLossList.removeUpTo(w_drop.seqno[DropRange::END]); - - // Note the rules: here `p` is pointing to the first block AFTER the - // message to be dropped, so the end sequence should be one behind - // the one for p. Note that the loop rolls until hitting the first - // packet that doesn't belong to the message or m_pLastBlock, which - // is past-the-end for the occupied range in the sender buffer. - SRT_ASSERT(w_drop.seqno[DropRange::END] == CSeqNo::decseq(p->m_iSeqNo)); - return READ_DROP; - } - - w_packet.pkt.m_pcData = p->m_pcData; - const int readlen = p->m_iLength; - w_packet.pkt.setLength(readlen, m_iBlockLen); - - // XXX Here the value predicted to be applied to PH_MSGNO field is extracted. - // As this function is predicted to extract the data to send as a rexmited packet, - // the packet must be in the form ready to send - so, in case of encryption, - // encrypted, and with all ENC flags already set. So, the first call to send - // the packet originally (extractUniquePacket) must set these flags first. - w_packet.pkt.set_msgflags(p->m_iMsgNoBitset); - w_srctime = p->m_tsOriginTime; - - // This function is called when packet retransmission is triggered. - // Therefore we are setting the rexmit time. - p->m_tsRexmitTime = steady_clock::now(); - - HLOGC(bslog.Debug, - log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.pkt.seqno() - << " size=" << readlen << " to send [REXMIT]"); - - return readlen; -} - -sync::steady_clock::time_point CSndBuffer::getRexmitTime(const int32_t seqno) -{ - ScopedLock bufferguard(m_BufLock); - const Block* p = m_pFirstBlock; - - int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); - if (offset < 0 || offset >= m_iCount) - return sync::steady_clock::time_point(); - - // XXX Suboptimal procedure to keep the blocks identifiable - // by sequence number. Consider using some circular buffer. - for (int i = 0; i < offset; ++i) - { - SRT_ASSERT(p); - p = p->m_pNext; - } - - SRT_ASSERT(p); - return p->m_tsRexmitTime; -} - -bool CSndBuffer::revoke(int32_t seqno) -{ - ScopedLock bufferguard(m_BufLock); - - int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); - - // IF distance between m_iSndLastDataAck and ack is nonempty... - if (offset <= 0) - return false; - - // remove any loss that predates 'ack' (not to be considered loss anymore) - m_SndLossList.removeUpTo(CSeqNo::decseq(seqno)); - - // We don't check if the sequence is past the last scheduled one; - // worst case scenario we'll just clear up the whole buffer. - m_iSndLastDataAck = seqno; - - bool move = false; - for (int i = 0; i < offset; ++i) - { - m_iBytesCount -= m_pFirstBlock->m_iLength; - if (m_pFirstBlock == m_pCurrBlock) - move = true; - m_pFirstBlock = m_pFirstBlock->m_pNext; - } - if (move) - m_pCurrBlock = m_pFirstBlock; - - m_iCount = m_iCount - offset; - - updAvgBufSize(steady_clock::now()); - return true; -} - -int32_t CSndBuffer::popLostSeq(DropRange& w_drop) -{ - static const DropRange nodrop = { {SRT_SEQNO_NONE, SRT_SEQNO_NONE}, SRT_MSGNO_CONTROL }; - w_drop = nodrop; - - // First attempt returned nothing, so return nothing and nodrop. - int32_t seq = m_SndLossList.popLostSeq(); - if (seq == SRT_SEQNO_NONE) - return seq; - - if (CSeqNo::seqoff(m_iSndLastDataAck, seq) < 0) - { - // Always request dropping up to the currently earliest remembered - // sequence number in the buffer. The only other thing needed to be - // cleaned up is to remove those outdated seqs from the loss list. - w_drop.seqno[DropRange::BEGIN] = seq; - w_drop.seqno[DropRange::END] = m_iSndLastDataAck; - - // In case when the loss record contains any sequences - // behind m_iSndLastDataAck, collect and drop them. - for (;;) - { - seq = m_SndLossList.popLostSeq(); - if (seq == SRT_SEQNO_NONE || CSeqNo::seqoff(m_iSndLastDataAck, seq) >= 0) - { - break; - } - } - } - - return seq; -} - -void CSndBuffer::removeLossUpTo(int32_t seqno) -{ - m_SndLossList.removeUpTo(seqno); -} - -int CSndBuffer::insertLoss(int32_t lo, int32_t hi, const sync::steady_clock::time_point& pt SRT_ATR_UNUSED) -{ - return m_SndLossList.insert(lo, hi); -} - -int CSndBuffer::getLossLength() -{ - return m_SndLossList.getLossLength(); -} - -int CSndBuffer::getCurrBufSize() const -{ - return m_iCount; -} - -int CSndBuffer::getMaxPacketLen() const -{ - return m_iBlockLen - m_iReservedSize; -} - -int CSndBuffer::countNumPacketsRequired(int iPldLen) const -{ - const int iPktLen = getMaxPacketLen(); - return number_slices(iPldLen, iPktLen); -} - -int CSndBuffer::getAvgBufSize(int& w_bytes, int& w_tsp) -{ - ScopedLock bufferguard(m_BufLock); /* Consistency of pkts vs. bytes vs. spantime */ - - /* update stats in case there was no add/ack activity lately */ - updAvgBufSize(steady_clock::now()); - - // Average number of packets and timespan could be small, - // so rounding is beneficial, while for the number of - // bytes in the buffer is a higher value, so rounding can be omitted, - // but probably better to round all three values. - - // Using simple rounding, as it should be guaranteed that - // these values are never negative. If they are, the results - // would be stupid anyway. - w_bytes = m_mavg.bytes() + 0.49; - w_tsp = m_mavg.timespan_ms() + 0.49; - return int(m_mavg.pkts() + 0.49); -} - -void CSndBuffer::updAvgBufSize(const steady_clock::time_point& now) -{ - if (!m_mavg.isTimeToUpdate(now)) - return; - - int bytes = 0; - int timespan_ms = 0; - const int pkts = getCurrBufSize((bytes), (timespan_ms)); - m_mavg.update(now, pkts, bytes, timespan_ms); -} - -int CSndBuffer::getCurrBufSize(int& w_bytes, int& w_timespan) const -{ - w_bytes = m_iBytesCount; - /* - * Timespan can be less then 1000 us (1 ms) if few packets. - * Also, if there is only one pkt in buffer, the time difference will be 0. - * Therefore, always add 1 ms if not empty. - */ - if (m_iCount > 0) - w_timespan = count_milliseconds(m_tsLastOriginTime - m_pFirstBlock->m_tsOriginTime) + 1; - else - w_timespan = 0; - - return m_iCount; -} - -CSndBuffer::duration CSndBuffer::getBufferingDelay(const time_point& tnow) const -{ - ScopedLock lck(m_BufLock); - SRT_ASSERT(m_pFirstBlock); - if (m_iCount == 0) - return duration(0); - - return tnow - m_pFirstBlock->m_tsOriginTime; -} - -int CSndBuffer::dropLateData(int& w_bytes, int32_t& w_first_msgno, const steady_clock::time_point& too_late_time) -{ - int dpkts = 0; - int dbytes = 0; - bool move = false; - int32_t msgno = 0; - - ScopedLock bufferguard(m_BufLock); - for (int i = 0; i < m_iCount && m_pFirstBlock->m_tsOriginTime < too_late_time; ++i) - { - dpkts++; - dbytes += m_pFirstBlock->m_iLength; - msgno = m_pFirstBlock->getMsgSeq(); - - if (m_pFirstBlock == m_pCurrBlock) - move = true; - m_pFirstBlock = m_pFirstBlock->m_pNext; - } - - if (move) - { - m_pCurrBlock = m_pFirstBlock; - } - - if (dpkts) - { - m_iCount = m_iCount - dpkts; - - const int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, dpkts); - m_iSndLastDataAck = fakeack; - - const int32_t minlastack = CSeqNo::decseq(m_iSndLastDataAck); - m_SndLossList.removeUpTo(minlastack); - - m_iBytesCount -= dbytes; - } - - w_bytes = dbytes; // even if 0 - - // We report the increased number towards the last ever seen - // by the loop, as this last one is the last received. So remained - // (even if "should remain") is the first after the last removed one. - w_first_msgno = ++MsgNo(msgno); - // Note: this will be 1 if no packets were removed, but in this case - // dpkts == 0 and the referenced results will not be interpreted. - - updAvgBufSize(steady_clock::now()); - - return dpkts; -} - -int CSndBuffer::dropAll(int& w_bytes) -{ - ScopedLock bufferguard(m_BufLock); - const int dpkts = m_iCount; - w_bytes = m_iBytesCount; - m_pFirstBlock = m_pCurrBlock = m_pLastBlock; - m_iCount = 0; - m_iBytesCount = 0; - updAvgBufSize(steady_clock::now()); - return dpkts; -} - -CSndBuffer::~CSndBuffer() -{ - Block* pb = m_pBlock->m_pNext; - while (pb != m_pBlock) - { - Block* temp = pb; - pb = pb->m_pNext; - delete temp; - } - delete m_pBlock; - - while (m_pFirstMemSlice != NULL) - { - MemSlice* temp = m_pFirstMemSlice; - m_pFirstMemSlice = m_pFirstMemSlice->m_pNext; - delete[] temp->m_pcData; - delete temp; - } - - releaseMutex(m_BufLock); -} - -// This does the same as increase(); try to find common parts or make -// these two common. -void CSndBuffer::initialize() -{ - // initial physical buffer of "size" - m_pFirstMemSlice = new MemSlice; - m_pFirstMemSlice->m_pcData = new char[m_iSize * m_iBlockLen]; - m_pFirstMemSlice->m_iSize = m_iSize; - m_pFirstMemSlice->m_pNext = NULL; - - // circular linked list for out bound packets - m_pBlock = new Block; - Block* pb = m_pBlock; - char* pslice = m_pFirstMemSlice->m_pcData; - - for (int i = 0; i < m_iSize; ++i) - { - pb->m_iMsgNoBitset = 0; - pb->m_pcData = pslice; - pslice += m_iBlockLen; - - if (i < m_iSize - 1) - { - pb->m_pNext = new Block; - pb = pb->m_pNext; - } - } - pb->m_pNext = m_pBlock; - - m_pFirstBlock = m_pCurrBlock = m_pLastBlock = m_pBlock; - -} - -void CSndBuffer::increase() -{ - int unitsize = m_pFirstMemSlice->m_iSize; - - // new physical buffer - MemSlice* nbuf = NULL; - try - { - nbuf = new MemSlice; - nbuf->m_pcData = new char[unitsize * m_iBlockLen]; - } - catch (...) - { - delete nbuf; - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); - } - nbuf->m_iSize = unitsize; - nbuf->m_pNext = NULL; - - // insert the buffer at the end of the buffer list - MemSlice* p = m_pFirstMemSlice; - while (p->m_pNext != NULL) - p = p->m_pNext; - p->m_pNext = nbuf; - - // new packet blocks - Block* nblk = NULL; - try - { - nblk = new Block; - } - catch (...) - { - delete nblk; - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); - } - Block* pb = nblk; - for (int i = 1; i < unitsize; ++i) - { - pb->m_pNext = new Block; - pb = pb->m_pNext; - } - - // insert the new blocks onto the existing one - pb->m_pNext = m_pLastBlock->m_pNext; - m_pLastBlock->m_pNext = nblk; - - pb = nblk; - char* pslice = nbuf->m_pcData; - for (int i = 0; i < unitsize; ++i) - { - pb->m_pcData = pslice; - pb = pb->m_pNext; - pslice += m_iBlockLen; - } - - m_iSize += unitsize; - - HLOGC(bslog.Debug, - log << "CSndBuffer: BUFFER FULL - adding " << (unitsize * m_iBlockLen) << " bytes spread to " << unitsize - << " blocks" - << " (total size: " << m_iSize << " bytes)"); -} - -std::string CSndBuffer::show() const -{ - using namespace hvu; - - ofmtbufstream out; - - int offset = 0; - int seqno = m_iSndLastDataAck; - - fmtc findex = fmtc().width(3).fillzero(); - - Block* p = m_pFirstBlock; - - while (p != m_pLastBlock) - { - out << "[" << fmt(offset, findex) << "]%" << seqno << ": " - << p->m_iLength << "!" << BufferStamp(p->m_pcData, p->m_iLength); - - out.puts(); - ++offset; - seqno = CSeqNo::incseq(seqno); - p = p->m_pNext; - } - - return out.str(); - -} - -// Stubs, unused in old buffer - -bool CSndBuffer::cancelLostSeq(int32_t) { return false; } - -#endif } // namespace srt diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index 5dd790695..e6d016d62 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -60,8 +60,6 @@ modified by #include "buffer_tools.h" #include "list.h" -#define SRT_SNDBUF_NEW 1 - // The notation used for "circular numbers" in comments: // The "cicrular numbers" are numbers that when increased up to the // maximum become zero, and similarly, when the zero value is decreased, @@ -100,11 +98,10 @@ struct CSndBlock } }; -#if SRT_SNDBUF_NEW - struct SndPktArray { typedef sync::steady_clock::time_point time_point; + typedef sync::steady_clock::duration duration; // Note: this structure has no constructor, so fields must be updated // upon creation. Currently only m_PktQueue.push_back() calls do this. @@ -138,6 +135,29 @@ struct SndPktArray // Busy flag. This is set by the extractor to mark this cell // as must-stay, until it's cleared. int m_iBusy; + + // This function must ensure that m_tsNextRexmitTime, if set, + // is distant by at least `miniv` towards m_tsRexmitTime; if not, + // m_tsNextRexmitTime will be updated to the minimum acceptable value. + // Returns: + // - false: the next rexmit time is in the future + // - true: the next rexmit time is in the past, or we don't care (if miniv is zero) + bool updated_rexmit_time_passed(const time_point& now, const duration& miniv) + { + // We don't check this; just make sure about that during the call + // --- [[assert (!is_zero(m_tsNextRexmitTime)]] + + // 1. Fix m_tsNextRexmitTime if it's too early after m_tsRexmitTime. + // 2. After that, check if m_tsNextRexmitTime is in the past. + if (miniv != duration() && !sync::is_zero(m_tsRexmitTime)) + { + const duration rxiv = m_tsNextRexmitTime - m_tsRexmitTime; + if (rxiv < miniv) + m_tsNextRexmitTime = m_tsRexmitTime + miniv; + } + + return m_tsNextRexmitTime < now; + } }; private: @@ -240,10 +260,18 @@ struct SndPktArray ~SndPktArray(); + // Expose for TESTING PURPOSES int unique_size() const { return m_iNewQueued; } int first_loss() const { return m_iFirstRexmit; } int last_loss() const { return m_iLastRexmit; } + void force_next_time(int offset, const time_point& newtime) + { + // NOTE: offset is not checked here. Use for testing only! + m_PktQueue[offset].m_tsNextRexmitTime = newtime; + } + + // GET, means the packet is still in the container, you just get access to it. // This call however changes the status of the retrieved packet by removing it // from the unique range and moving it to the history range. @@ -265,10 +293,9 @@ struct SndPktArray bool clear_loss(int index); - //void remove_loss_seq(int32_t seqhi); - bool insert_loss(int ixlo, int ixhi, const time_point& = sync::steady_clock::now()); + bool insert_loss(int ixlo, int ixhi, const time_point& nowtime = sync::steady_clock::now()); - void set_rexmit_time(int ixlo, int ixhi, const time_point& time) + void update_next_rexmit_time(int ixlo, int ixhi, const time_point& time) { for (int i = ixlo; i <= ixhi; ++i) { @@ -282,7 +309,7 @@ struct SndPktArray int loss_length() const { return m_iLossLengthCache; } - int extractFirstLoss(); + int extractFirstLoss(const duration& min_interval = duration()); size_t size() const { @@ -356,8 +383,6 @@ struct SndPktArray bool validateLossIntegrity(std::string& msg); }; -#else -#endif struct CSndPacket { @@ -472,9 +497,21 @@ class CSndBuffer int32_t msgno; }; + // THIS IS FOR TESTING PURPOSES ONLY. In the normal code + // the retransmission extraction is done through extractFirstRexmitPacket, + // which extracts the packet marked loss in the buffer and fills the packet in one call. SRT_TSA_NEEDS_NONLOCKED(m_BufLock) int readOldPacket(int32_t seqno, CSndPacket& w_packet, time_point& w_origintime, DropRange& w_drop); + int extractFirstRexmitPacket(const duration& min_rexmit_interval, int32_t& w_current_seqno, CSndPacket& w_sndpkt, + sync::steady_clock::time_point& w_tsOrigin, std::vector& w_drops); + +private: + SRT_TSA_NEEDS_LOCKED(m_BufLock) + int readPacketInternal(int offset, CSndPacket& w_packet, time_point& w_origintime, DropRange& w_drop); + +public: + /// Get the time of the last retransmission (if any) of the DATA packet. /// @param [in] offset offset from the last ACK point (backward sequence number difference) /// @@ -501,15 +538,11 @@ class CSndBuffer int getAvgBufSize(int& bytes, int& timespan); -#if SRT_SNDBUF_NEW int getCurrBufSize(int& bytes, int& timespan) const { sync::ScopedLock lk (m_BufLock); return getBufferStats((bytes), (timespan)); } -#else - int getCurrBufSize(int& bytes, int& timespan) const; -#endif /// Retrieve input bitrate in bytes per second int getInputRate() const { return m_rateEstimator.getInputRate(); } @@ -542,6 +575,8 @@ class CSndBuffer // Sender loss list management methods void removeLossUpTo(int32_t seqno); int insertLoss(int32_t lo, int32_t hi, const sync::steady_clock::time_point& pt = sync::steady_clock::time_point()); + + // For testing purposes only. Not used in the code. int32_t popLostSeq(DropRange&); int getLossLength(); @@ -581,8 +616,6 @@ class CSndBuffer void releasePacket(int32_t seqno); -#if SRT_SNDBUF_NEW - /// Buffer capacity (maximum size), used intermediately and in initialization only. int m_iCapacity; @@ -590,40 +623,6 @@ class CSndBuffer int getBufferStats(int& bytes, int& timespan) const; -#else - void increase(); - - CSndLossList m_SndLossList; // Sender loss list - - struct Block: CSndBlock - { - Block* m_pNext; // next block - }; - - Block* m_pBlock; - Block* m_pFirstBlock; - Block* m_pCurrBlock; - Block* m_pLastBlock; - - // m_pBlock: The head pointer - // m_pFirstBlock: The first block - // m_pCurrBlock: The current block - // m_pLastBlock: The last block (if first == last, buffer is empty) - - struct MemSlice - { - char* m_pcData; // buffer - int m_iSize; // size - MemSlice* m_pNext; // next buffer - } * m_pFirstMemSlice; // physical buffer - - int m_iSize; // buffer size (number of packets) - // NOTE: This is atomic AND under lock because the function getCurrBufSize() - // is returning it WITHOUT locking. Modification, however, must stay under - // a lock. - sync::atomic m_iCount; // number of used blocks - -#endif // deleted copyers diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 92c213c26..637ae486c 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9090,7 +9090,7 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << losslist_lo << " - " << losslist_hi << " to loss list"); - num = m_pSndBuffer->insertLoss(losslist_lo, losslist_hi); + num = m_pSndBuffer->insertLoss(losslist_lo, losslist_hi, steady_clock::now()); } // ELSE losslist_lo %< m_iSndLastAck else @@ -9114,7 +9114,7 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << m_iSndLastAck << "[ACK] - " << losslist_hi << " to loss list"); - num = m_pSndBuffer->insertLoss(m_iSndLastAck, losslist_hi); + num = m_pSndBuffer->insertLoss(m_iSndLastAck, losslist_hi, steady_clock::now()); dropreq_hi = CSeqNo::decseq(m_iSndLastAck); IF_HEAVY_LOGGING(drop_type = "partially"); } @@ -9156,7 +9156,7 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding %" << losslist[i] << " (1 packet) to loss list"); - const int num = m_pSndBuffer->insertLoss(losslist[i], losslist[i]); + const int num = m_pSndBuffer->insertLoss(losslist[i], losslist[i], steady_clock::now()); enterCS(m_StatsLock); m_stats.sndr.lost.count(num); @@ -9668,139 +9668,6 @@ void CUDT::updateAfterSrtHandshake(int hsv) } } -// XXX During sender buffer refax turn this into either returning -// a sequence number or move it to the sender buffer facility. -// [[using locked (m_RecvAckLock)]] -int32_t CUDT::getCleanRexmitOffset() -{ - // This function is required to look into the loss list and - // drop all sequences that are alrady revoked from the sender - // buffer, send the drop request if needed, and return the sender - // buffer offset for the next packet to retransmit, or -1 if - // there is no retransmission candidate at the moment. - - for (;;) - { - // REPEATABLE because we might need to drop this scheduled seq. - typedef CSndBuffer::DropRange DropRange; - DropRange buffer_drop; - - int32_t seq = m_pSndBuffer->popLostSeq((buffer_drop)); - - // The droppable information MAY be reported regardless if any - // lost sequence was found. - if (buffer_drop.seqno[DropRange::BEGIN] != SRT_SEQNO_NONE) - { - int32_t seqpair[2] = { - buffer_drop.seqno[DropRange::BEGIN], - buffer_drop.seqno[DropRange::END] - }; - - HLOGC(qslog.Debug, - log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: %(" - << seqpair[0] << " - " << seqpair[1] << ")"); - - // See interpretation in processCtrlDropReq(). We don't know the message number, - // so we request that the drop be exclusively sequence number based. - int32_t msgno = SRT_MSGNO_CONTROL; - sendCtrl(UMSG_DROPREQ, &msgno, seqpair, sizeof(seqpair)); - } - - // Pick up the first sequence. - if (seq == SRT_SEQNO_NONE) - { - // No loss reports, we are clear here. - return seq; - } - - // For the right sequence though check also the rigt time. - const steady_clock::time_point time_now = steady_clock::now(); - if (!checkRexmitRightTime(seq, time_now)) - { - // Ignore this, even though valid, sequence, and pick up - // the next from the list. - continue; - } - - // Ok, we have what we needed one way or another. - return seq; - } -} - -// [[using locked (m_RecvAckLock)]] -bool srt::CUDT::checkRexmitRightTime(int32_t seqno, const srt::sync::steady_clock::time_point& current_time) -{ - // If not configured, the time is always right - if (!m_bPeerNakReport || m_config.iRetransmitAlgo == 0) - return true; - - const steady_clock::time_point time_nak = current_time - microseconds_from(m_iSRTT - 4 * m_iRTTVar); - const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getRexmitTime(seqno); - - if (tsLastRexmit >= time_nak) - { -#if HVU_ENABLE_HEAVY_LOGGING - ostringstream last_rextime; - if (is_zero(tsLastRexmit)) - { - last_rextime << "REXMIT FIRST TIME"; - } - else - { - last_rextime << "REXMITTED " << FormatDuration(current_time - tsLastRexmit) - << " ago at " << FormatTime(tsLastRexmit); - } - HLOGC(qslog.Debug, log << CONID() << "REXMIT: ignoring %" << seqno - << " " << last_rextime.str() - << ", RTT: ~" << FormatValue(m_iSRTT, 1000, "ms") - << " <>" << FormatValue(m_iRTTVar, 1000, "") - << " now=" << FormatTime(current_time)); -#endif - return false; - } - - return true; -} - - -// [[using locked (m_RecvAckLock)]] -int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, CSndPacket& w_packet, srt::sync::steady_clock::time_point& w_tsOrigin) -{ - // REPEATABLE BLOCK (not a real loop) - // The call to readOldPacket may result in a drop request, which must be - // handled and then the call repeated, until it returns a valid packet - // or no packet to retransmit. - for (;;) - { - typedef CSndBuffer::DropRange DropRange; - DropRange buffer_drop; - - const int payload = m_pSndBuffer->readOldPacket(seqno, (w_packet), (w_tsOrigin), (buffer_drop)); - if (payload == CSndBuffer::READ_DROP) - { - SRT_ASSERT(CSeqNo::seqoff(buffer_drop.seqno[DropRange::BEGIN], buffer_drop.seqno[DropRange::END]) >= 0); - - HLOGC(qslog.Debug, - log << CONID() << "loss-reported packets expired in SndBuf - requesting DROP: #" - << buffer_drop.msgno << " %(" << buffer_drop.seqno[DropRange::BEGIN] << " - " - << buffer_drop.seqno[DropRange::END] << ")"); - sendCtrl(UMSG_DROPREQ, &buffer_drop.msgno, buffer_drop.seqno, sizeof(buffer_drop.seqno)); - - // skip all dropped packets - m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, buffer_drop.seqno[DropRange::END]); - continue; - } - - if (payload == CSndBuffer::READ_NONE) - { - LOGC(qslog.Error, log << CONID() << "loss-reported packet %" << seqno << " NOT FOUND in the sender buffer"); - return 0; - } - - return payload; - } - -} int CUDT::packLostData(CSndPacket& w_sndpkt) { @@ -9847,20 +9714,42 @@ int CUDT::packLostData(CSndPacket& w_sndpkt) { // protect m_iSndLastDataAck from updating by ACK processing ScopedLock ackguard(m_RecvAckLock); + typedef CSndBuffer::DropRange DropRange; - // Get the first sequence for retransmission, bypassing and taking care of - // those that are in the forgotten region, as well as required to be rejected. - int32_t seqno = getCleanRexmitOffset(); + std::vector drops; + duration rexmit_interval = duration(); + if (m_bPeerNakReport && m_config.iRetransmitAlgo != 0) + { + // Minimum required time interval since the last retransmission request. + rexmit_interval = optimisticRTT(); + } - if (seqno == SRT_SEQNO_NONE) - return 0; + // m_iSndCurrSeqNo is atomic, so we can't pass it by reference, we need a proxy + int32_t snd_curr_seqno_proxy = m_iSndCurrSeqNo; + const int payload = m_pSndBuffer->extractFirstRexmitPacket(rexmit_interval, (snd_curr_seqno_proxy), (w_sndpkt), (tsOrigin), (drops)); + m_iSndCurrSeqNo = snd_curr_seqno_proxy; + + if (!drops.empty()) + { + for (size_t i = 0; i < drops.size(); ++i) + { + CSndBuffer::DropRange& buffer_drop = drops[i]; + int32_t seqpair[2] = { + buffer_drop.seqno[DropRange::BEGIN], + buffer_drop.seqno[DropRange::END] + }; + + HLOGC(qslog.Debug, + log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: %(" + << seqpair[0] << " - " << seqpair[1] << ")"); - HLOGC(qslog.Debug, log << "REXMIT: got %" << seqno << ", requesting that packet from sndbuf with first %" - << m_pSndBuffer->firstSeqNo()); + // See interpretation in processCtrlDropReq(). We don't know the message number, + // so we request that the drop be exclusively sequence number based. + int32_t msgno = SRT_MSGNO_CONTROL; + sendCtrl(UMSG_DROPREQ, &msgno, seqpair, sizeof(seqpair)); + } + } - // Extract the packet from the sender buffer that is mapped to the expected sequence - // number, bypassing and taking care of those that are decided to be dropped. - const int payload = extractCleanRexmitPacket(seqno, (w_sndpkt), (tsOrigin)); if (payload <= 0) return 0; } @@ -12237,7 +12126,7 @@ void CUDT::checkRexmitTimer(const steady_clock::time_point& currtime) // Sender: Insert all the packets sent after last received acknowledgement into the sender loss list. // Resend all unacknowledged packets on timeout, but only if there is no packet in the loss list const int32_t csn = m_iSndCurrSeqNo; - const int num = m_pSndBuffer->insertLoss(m_iSndLastAck, csn); + const int num = m_pSndBuffer->insertLoss(m_iSndLastAck, csn, steady_clock::now()); if (num > 0) { enterCS(m_StatsLock); diff --git a/srtcore/core.h b/srtcore/core.h index 51af46fb5..71d0cf4bf 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -450,6 +450,8 @@ class CUDT bool isOPT_TsbPd() const { return m_config.bTSBPD; } int avgRTT() const { return m_iSRTT; } int RTTVar() const { return m_iRTTVar; } + duration optimisticRTT() const { return sync::microseconds_from(m_iSRTT - 4 * m_iRTTVar); } + SRT_TSA_NEEDS_LOCKED(m_RecvAckLock) int32_t sndSeqNo() const { return m_iSndCurrSeqNo; } int32_t schedSeqNo() const { return m_iSndNextSeqNo; } @@ -1324,9 +1326,6 @@ class CUDT /// @return payload size on success, <=0 on failure int packLostData(CSndPacket &packet); - int32_t getCleanRexmitOffset(); - bool checkRexmitRightTime(int32_t seqno, const sync::steady_clock::time_point& current_time); - int extractCleanRexmitPacket(int32_t seqno, CSndPacket& w_packet, sync::steady_clock::time_point& w_tsOrigin); /// Pack a unique data packet (never sent so far) in CPacket for sending. /// @param packet [in, out] a CPacket structure to fill. diff --git a/test/test_buffer_snd.cpp b/test/test_buffer_snd.cpp index 8d966815a..bc20de320 100644 --- a/test/test_buffer_snd.cpp +++ b/test/test_buffer_snd.cpp @@ -153,6 +153,8 @@ class TestSndLoss: public srt::Test srt::SndPktArray packets { 1024, 20, 20 }; + int show(); + protected: // SetUp() is run immediately before a test starts. @@ -172,6 +174,14 @@ class TestSndLoss: public srt::Test }; +// For manual execution in the debugger +int TestSndLoss::show() +{ + sout.puts(packets.show_external(10000)); + return 0; +} + + TEST_F(TestSndBuffer, Basic) { for (int i = 1; i < 11; ++i) @@ -1184,6 +1194,50 @@ TEST_F(TestSndLoss, Build_complex_then_remove_single_and_prefix) EXPECT_TRUE(packets.validateLossIntegrity((validmsg))) << ">>> " << validmsg; } +TEST_F(TestSndLoss, Jumpover_removal_and_selective_clearing) +{ + packets.insert_loss(6, 7); + + packets.insert_loss(9, 12); + + // Now insert 8, but with 1h ahead + packets.insert_loss(8, 8, steady_clock::now() + seconds_from(3600)); + + EXPECT_EQ(packets.loss_length(), 7); + + EXPECT_EQ(packets.extractFirstLoss(), 6); + EXPECT_EQ(packets.extractFirstLoss(), 7); + EXPECT_EQ(packets.extractFirstLoss(), 9); + EXPECT_EQ(packets.extractFirstLoss(), 10); + + // Ok, 8 should have been skipped, so it remains earliest, + // to be removed later. Note that cleared losses are still + // counted as existing. + packets.force_next_time(8, sync::steady_clock::now()); + + EXPECT_EQ(packets.extractFirstLoss(), 8); + + // 9, 10 have been extracted and cleared, but they still + // do count for the overall length, until they are jumped over. + EXPECT_EQ(packets.loss_length(), 4); + + packets.force_next_time(11, steady_clock::now() + seconds_from(3600)); + + EXPECT_EQ(packets.extractFirstLoss(), 12); + + packets.force_next_time(11, steady_clock::now()); + + EXPECT_EQ(packets.extractFirstLoss(), 11); + + // NOTE: stating that 12 has been retrieved earlier, + // we have now one dead loss. So try to retrieve a loss, + // confirm there is no more, but after that this dead + // record should be also cleared + EXPECT_EQ(packets.extractFirstLoss(), -1); + + EXPECT_EQ(packets.loss_length(), 0); +} + #endif From f17d9b96b81a4fe6efd32a76e173d0d0b30cd0b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 12 Dec 2025 13:11:34 +0100 Subject: [PATCH 23/26] Fixed bug in CRcvBuf: m_iEndOff incorrectly updated after removal. Reimplemented walkEntries and added test for it. Some fixes from CodeQL reports --- srtcore/buffer_rcv.cpp | 34 ++++++++++++++++----------------- srtcore/buffer_rcv.h | 41 ++++++++++++++++++++-------------------- srtcore/buffer_snd.cpp | 4 ---- srtcore/buffer_tools.h | 2 +- test/test_buffer_rcv.cpp | 34 +++++++++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 42 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index e8ade2bef..7a62f4e2f 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -174,20 +174,13 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) // If >= 2, then probably there is a long gap, and buffer needs to be reset. SRT_ASSERT((m_iStartPos + offset) / hsize() < 2); - const CPos newpktpos = incPos(m_iStartPos, offset); const COff prev_max_off = m_iMaxPosOff; - bool extended_end = false; - if (offset >= m_iMaxPosOff) - { - m_iMaxPosOff = offset + COff(1); - extended_end = true; - } + const CPos newpktpos = accessPos(offset); // Packet already exists - // (NOTE: the above extension of m_iMaxPosOff is - // possible even before checking that the packet - // exists because existence of a packet beyond - // the current max position is not possible). + // (NOTE: the above extension of m_iMaxPosOff in accessPos is possible even + // before checking that the packet exists because existence of a packet + // beyond the current max position is not possible). SRT_ASSERT(newpktpos >= 0 && newpktpos < int(hsize())); if (m_entries[newpktpos].status != EntryState_Empty) { @@ -205,7 +198,7 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) // Set to a value, if due to insertion there was added // a packet that is earlier to be retrieved than the earliest // currently available packet. - time_point earlier_time = updatePosInfo(unit, prev_max_off, offset, extended_end); + time_point earlier_time = updatePosInfo(unit, prev_max_off, offset); InsertInfo ireport (InsertInfo::INSERTED); ireport.first_time = earlier_time; @@ -292,12 +285,12 @@ void CRcvBuffer::getAvailInfo(CRcvBuffer::InsertInfo& w_if) // This function is called exclusively after packet insertion. // This will update also m_iEndOff and m_iDropOff fields (the latter // regardless of the TSBPD mode). -CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff prev_max_off, - const COff offset, - const bool extended_end) +CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff prev_max_off, const COff offset) { time_point earlier_time; + const bool extended_end = prev_max_off != m_iMaxPosOff; + // Update flags // Case [A]: insertion of the packet has extended the busy region. if (extended_end) @@ -805,7 +798,12 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair= 0); - m_iEndOff = decOff(m_iEndOff, len); + // Theoretically m_iEndOff should be shifted by the number of + // extracted packets; if it was one and the only packet, worst case + // this will make m_iEndOff == 1, nskipped == 1, so result is 0. + m_iEndOff = decOff(m_iEndOff, nskipped); + if (m_iDropOff) + m_iDropOff = decOff(m_iDropOff, nskipped); } countBytes(-pkts_read, -bytes_extracted); @@ -821,9 +819,11 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair - LoopStatus walkEntries(COff startoff, COff endoff, Callable fn) + COff walkEntries(COff startoff, COff endoff, Callable fn) { - SRT_ASSERT(startoff <= endoff && endoff <= hsize()); + SRT_ASSERT(startoff <= endoff && endoff <= COff(hsize())); if (startoff == endoff) return CONTINUE; @@ -296,17 +295,17 @@ struct CiBuffer value_type& e = m_entries[i]; LoopStatus st = fn(e); if (st == BREAK) - return BREAK; + return (i - startpos) + startoff; } - return CONTINUE; + return endoff; } - for (CPos i = startpos; i < hsize(); ++i) + for (CPos i = startpos; i < CPos(hsize()); ++i) { value_type& e = m_entries[i]; LoopStatus st = fn(e); if (st == BREAK) - return BREAK; + return (i - startpos) + startoff; } for (CPos i = CPos(0); i < endpos; ++i) @@ -314,18 +313,18 @@ struct CiBuffer value_type& e = m_entries[i]; LoopStatus st = fn(e); if (st == BREAK) - return BREAK; + return (i + hsize() - startpos) + startoff; } - return CONTINUE; + return endoff; } size_t hsize() const { return m_entries.size(); } - //CPos incPos(CPos pos, COff inc = COff(1)) const { return CPos((pos + inc) % hsize()); } CPos incPos(CPos position, COff offset = COff(1)) const { + // THEORETICAL implementation: (pos + inc) % hsize() CPos sum = position + offset; CPos posmax = hsize(); if (sum >= posmax) @@ -394,18 +393,20 @@ struct CiBuffer } }; - value_type* access(COff offset) + CPos accessPos(COff offset) { - if (offset >= hsize()) - return NULL; - if (offset >= m_iMaxPosOff) { walkEntries(m_iMaxPosOff, offset, ClearGapEntries()); - m_iMaxPosOff = offset + 1; + m_iMaxPosOff = offset + COff(1); } - return &m_entries[incPos(m_iStartPos, offset)]; + return incPos(m_iStartPos, offset); + } + + value_type& access(COff offset) + { + return m_entries[accessPos(offset)]; } void drop(COff offset) @@ -431,7 +432,7 @@ struct ReceiverBufferBase { enum EntryStatus { - EntryState_Empty, //< No CUnit record. + EntryState_Empty = 0, //< No CUnit record. EntryState_Avail, //< Entry is available for reading. EntryState_Read, //< Entry has already been read (out of order). EntryState_Drop //< Entry has been dropped. @@ -536,7 +537,7 @@ class CRcvBuffer: public ReceiverBufferBase, private CiBuffer< FixedArray > m_iStartSeqNo; COff m_iEndOff; // past-the-end of the contiguous region since m_iStartOff - COff m_iDropOff; // points past m_iEndOff to the first deliverable after a gap, or == m_iEndOff if no such packet + COff m_iDropOff; // points past m_iEndOff to the first deliverable after a gap; value 0 if no first gap CPos m_iFirstNonreadPos; // First position that can't be read (<= m_iLastAckPos) int m_iNotch; // the starting read point of the first unit diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 541fd9421..a88b035ad 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -1130,17 +1130,13 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne if (offset_lo < 0) { - //int fix = 0 - offset_lo; offset_lo = 0; - //seqlo = CSeqNo::incseq(seqlo, fix); } // It was checked that size() is at least 1 if (offset_hi >= int(m_PktQueue.size())) { - //int fix = offset_hi - m_PktQueue.size(); offset_hi = m_PktQueue.size() - 1; - //seqhi = CSeqNo::decseq(seqhi, fix); } HLOGC(bslog.Debug, log << "insert_loss: INSERTING offset " << offset_lo << "..." << offset_hi); diff --git a/srtcore/buffer_tools.h b/srtcore/buffer_tools.h index 876f1c678..22fe6c053 100644 --- a/srtcore/buffer_tools.h +++ b/srtcore/buffer_tools.h @@ -131,7 +131,7 @@ class CRateEstimator void restoreFrom(const Saver& o) { updateFrom(o); } private: - CRateEstimator& operator=(const CRateEstimator&); // = delete; + CRateEstimator& operator=(const CRateEstimator&); // in C++11: = delete // Constants static const uint64_t INPUTRATE_FAST_START_US = 500000; // 500 ms diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp index 065ffdd52..a0852e4dc 100644 --- a/test/test_buffer_rcv.cpp +++ b/test/test_buffer_rcv.cpp @@ -956,3 +956,37 @@ TEST_F(CRcvBufferReadStream, ReadFractional) EXPECT_EQ(m_unit_queue->size(), m_unit_queue->capacity()); } + +TEST(CRcvBufferInternal, EntryLoop) +{ + typedef std::vector Container; + + CiBuffer cibuffer(32); + using LoopStatus = typename CiBuffer::LoopStatus; + + cibuffer.access(20) = "one"; + cibuffer.access(22) = "two"; + + std::string out; + + size_t lastx = cibuffer.walkEntries(15, 23, [&out] (std::string& cell) { + out += cell; + return LoopStatus::CONTINUE; + }); + + EXPECT_EQ(lastx, 23); + EXPECT_EQ(out, "onetwo"s); + + cibuffer.drop(15); + EXPECT_EQ(cibuffer.m_iStartPos, 15); + + lastx = cibuffer.walkEntries(3, 10, [&out] (std::string& cell) { + out += cell; + return LoopStatus::CONTINUE; + }); + + EXPECT_EQ(lastx, 10); + +} + + From bad36769fe8207e4961081515af3dbf54dfa8fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 12 Dec 2025 13:26:53 +0100 Subject: [PATCH 24/26] Fixed build break on pedantic configurations --- test/test_buffer_rcv.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp index a0852e4dc..2ddf0fca8 100644 --- a/test/test_buffer_rcv.cpp +++ b/test/test_buffer_rcv.cpp @@ -974,7 +974,7 @@ TEST(CRcvBufferInternal, EntryLoop) return LoopStatus::CONTINUE; }); - EXPECT_EQ(lastx, 23); + EXPECT_EQ(lastx, size_t(23)); EXPECT_EQ(out, "onetwo"s); cibuffer.drop(15); @@ -985,7 +985,7 @@ TEST(CRcvBufferInternal, EntryLoop) return LoopStatus::CONTINUE; }); - EXPECT_EQ(lastx, 10); + EXPECT_EQ(lastx, size_t(10)); } From f1e5b8982e7f862028e80dcaef82872cf2ed8b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 15 Dec 2025 14:38:16 +0100 Subject: [PATCH 25/26] Literal in comment --- srtcore/core.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srtcore/core.h b/srtcore/core.h index 423a3bcf3..52d01f6e8 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -244,7 +244,7 @@ class CLastSched m_tsTime = currtime; // Still false because we have just one packet here, - // so once this is scheduled, thereś nothing more. + // so once this is scheduled, there's nothing more. m_bChain = false; return true; } From 9468f925eec6900cbda693976b46cbe70ae7cb6d Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Thu, 9 Apr 2026 09:40:47 +0200 Subject: [PATCH 26/26] Fixed: deleted outdated doc file --- docs/dev/containers.md | 322 ----------------------------------------- 1 file changed, 322 deletions(-) delete mode 100644 docs/dev/containers.md diff --git a/docs/dev/containers.md b/docs/dev/containers.md deleted file mode 100644 index 7eb35b882..000000000 --- a/docs/dev/containers.md +++ /dev/null @@ -1,322 +0,0 @@ -Containers -========== - -Receiver buffer ---------------- - -The receiver buffer is based on the circular buffer with appropriate -enhancements. - -The circular buffer contains Entry internal type, which entails the status -and possibly the Unit containing the data. Units are supplied by a multiplexer -and placed by pointer here. - -Every time a packet comes in over the connection, which may be a control or -data packet, it's written into the buffer contained in a Unit. If such a unit -is filled with the control data, the control command is handled and the unit is -reused. If it contained the data, this is passed to the receiver buffer. The -position in the buffer is determined by the sequence number of the incoming -packet. The position is validated first, and if it turns out to be not filled -in before and within the expected range of arriving sequence numbers, the unit -filled during extraction of the data from the network is placed by pointer -into the appropriate position in the buffer. A unit that has been pinned into -the buffer is marked busy, and after extraction is marked free. - -On the other end packets are being extracted using various ways depending on -the mode: - -* live mode: always one packet at a time at position 0. If dropping is enabled, - the empty positions are just skipped until the readable packet is reached. - -* message mode: extraction is possible only if the whole message is - reassembled, although it may happen that there is available a complete - message, just not at the beginning of the buffer. If this message has - the `inorder` flag clear, it is allowed to be delivered even if it would - mean an out-of-order delivery. If this happens, all packets of this message - are marked as Read so that if the earlier message is finally completed, - after extracting this message the read messages directly following it - are also removed from the buffer - -* stream mode: extracted are as much packets from the ICR region as available - and fit in the given buffer. - -This is a rough schema of the receiver buffer: - -* ICR = Initial Contiguous Region: all cells here contain valid packets -* SCRAP REGION: Region with possibly filled or empty cells - - NOTE: in scrap region, the first cell is empty and the last one filled. -* SPARE REGION: Region without packets - -``` - - | BUSY REGION | - | | | | - | ICR | SCRAP REGION | SPARE REGION...-> - ......->| | | | - | /FIRST-GAP | | - |<------------------- hsize() ---------------------------->| - | |<------------ m_iMaxPosOff ----------->| | - | | | | | | - +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ - | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] - +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ - | | | | - | | | \__last pkt received - |<------------->| m_iDropOff | - | | | - |<--------->| m_iEndOff | - | - \___ m_iStartPos: first packet position in the buffer - -``` - -Container value type is ReceiverBufferBase::Entry, which contains two -fields: - -* `pUnit`: points to the Unit, or NULL if empty -* `status`: the cell's status - -Status values used normally (live and stream mode): - -* `EntryState_Empty`: No packet here (pUnit == NULL). This is the default - state before getting a packet and the state of the units in the spare region - -* `EntryState_Avail`: The packet is ready for reading. It is set just after - the position has been confirmed and the unit pointer written into `pUnit` - -In message mode additionally two other states are possible: - -* `EntryState_Read`: The packet was prematurely extracted by out-of-order - reading. The space must be still occupied because it's in the scrap region - following some fragmented message, which still waits for reassembly - -* `EntryState_Drop`: The message was requested to be dropped. This usually - happens when the timeout for the message passed and the message is still - not reassembled, or when the message was revoked from the sender buffer - by the peer, so the peer has sent the `UMSG_DROPREQ`, making the packets - no longer recoverable. If this happens, all messages containing dropped - packets are marked Drop state and will no longer be delivered - -Position fields from the CiBuffer container: - -* `m_iStartPos`: physical container's index with the logical position 0 -* `m_iMaxPosOff`: past-the-end offset value (NOT position!) of the occupied region - -The container normally expects packets to be there in the order of their -sequence numbers. However, potentially packets can come out of order or -get lost in the UDP link; we can have then gaps in the form of unfilled -cells in the buffer. Hence we have 3 important regions: - -* ICR: This starts with the cell 0 and ends with the next empty cell. - ICR can be empty if the cell 0 is empty - -* FIRST GAP: Starts with the very first empty cell in the buffer - and follows until the first found filled cell. If the first empty - cell following the ICR is at `m_iMaxPosOff` potition, the first - gap is empty - -* DROP REGION: Starts with the first filled cell after a series of - empty cells of the FIRST GAP. Empty, if FIRST GAP is empty. - -These positions are marked with the following fields: - -* `m_iEndOff`: offset to the end of ICR. This points always to an empty cell. - If this value is 0, the buffer has an empty ICR. If this value is equal to - `m_iMaxPosOff`, the whole buffer is contiguous (no FIRST GAP or DROP REGION) - -* `m_iDropOff`: offset to a packet available for retrieval after a drop. If 0, - the DROP REGION is empty. DROP REGION is the fragment of the SCRAP REGION - that begins with at least one valid packet - -The DROP REGION is determined in order to quickly perform the dropping -operation. In case when the last packet has been extracted from the ICR, the -cell 0 is empty. When the decision for an extraction-over-consistency is -undertaken (live mode with too-late-packet-drop enabled, when the play time -comes for the first packet in the DROP REGION), the whole FIRST GAP is removed -from the buffer so the beginning of the buffer shifts to the DROP REGION, which -becomes ICR this way, and then the valid packet at cell 0 is extracted. - -Operational rules: - -Initially: - -``` - m_iStartPos = 0 - m_iEndOff = 0 - m_iDropOff = 0 -``` - -When a packet has arrived, then depending on where it landed: - -1. Position: next to the last read one and newest - -* `m_iStartPos` unchanged. -* `m_iEndOff` shifted by 1 -* `m_iDropOff` = 0 - -2. Position: after a loss, newest. - -* `m_iStartPos` unchanged. -* `m_iEndOff` unchanged. -* `m_iDropOff`: - - set to this packet, if `m_iDropOff` == 0 - - otherwise unchanged - -3. Position: after a loss, but belated (retransmitted) -- not equal to `m_iEndPos` - -* `m_iStartPos` unchanged. -* `m_iEndOff` unchanged. -* `m_iDropOff`: - - if `m_iDropOff` == 0, set to this - - if `m_iDropOff` is past this packet, set to this - - otherwise unchanged - -4. Position: after a loss, sealing -- seq equal to position of `m_iEndOff` - -* `m_iStartPos` unchanged. -* `m_iEndOff`: - - since this position, search the first empty cell - - stop at `m_iMaxPosOff` or first empty cell, whichever is found first -* `m_iDropOff`: - - if `m_iEndOff` == `m_iMaxPosOff`, set it to 0 - - otherwise search for a nonempty cell since `m_iEndOff` - - walk at maximum to `m_iMaxPosOff` - -NOTE: - -If DROP REGION is empty, then `m_iMaxPosOff` == `m_iEndOff`. If there is one -existing packet, then one loss, then two existing packets, the offsets are: - -* `m_iEndOff` = 1 -* `m_iDropOff` = 2 -* `m_iMaxPosOff` = 4 - -To wrap up: - -Let's say we have the following possibilities in a general scheme: - - -``` - [D] [C] [B] [A] (insertion cases) - | (start) --- (end) ===[gap]=== (after-loss) ... (max-pos) | - ICR FIRST GAP -``` - -See the CRcvBuffer::updatePosInfo method for detailed implementation. - -WHEN INSERTING A NEW PACKET: - -If the incoming sequence maps to newpktpos that is: - -* newpktpos <% (start) : discard the packet and exit -* newpktpos %> (size) : report discrepancy, discard and exit -* newpktpos %> (start) and: - * EXISTS: discard and exit (NOTE: could be also < (end)) - -``` -[A]* seq == `m_iMaxPosOff` - --> INC `m_iMaxPosOff` - * `m_iEndPos` == previous `m_iMaxPosOff` - * previous `m_iMaxPosOff` + 1 == `m_iMaxPosOff` - --> `m_iEndPos` = `m_iMaxPosOff` - --> `m_iDropPos` = `m_iEndPos` - * otherwise (means the new packet caused a gap) - --> `m_iEndPos` REMAINS UNCHANGED - --> `m_iDropPos` = POSITION(`m_iMaxPosOff`) -``` -COMMENT: - -If this above condition isn't satisfied, then there are gaps, first at -`m_iEndOff`, and `m_iDropOff` is at furthest equal to `m_iMaxPosOff`-1. The -inserted packet is outside both the contiguous region and the following -scratched region, so no updates on `m_iEndPos` and `m_iDropPos` are necessary. - -NOTE: - -SINCE THIS PLACE seq cannot be a sequence of an existing packet, -which means that earliest offset(newpktpos) == `m_iEndOff`, -up to == `m_iMaxPosOff` - 2. - -``` - * otherwise (newpktpos <% max-pos): - [D]* newpktpos == `m_iEndPos`: - --> (search FIRST GAP and FIRST AFTER-GAP) - --> `m_iEndPos`: increase until reaching `m_iMaxPosOff` - * `m_iEndPos` <% `m_iMaxPosOff`: - --> `m_iDropPos` = first VALID packet since `m_iEndPos` +% 1 - * otherwise: - --> `m_iDropPos` = `m_iEndPos` - [B]* newpktpos %> `m_iDropPos` - --> store, but do not update anything - [C]* otherwise (newpktpos %> `m_iEndPos` && newpktpos <% `m_iDropPos`) - --> store - --> set `m_iDropPos` = newpktpos -``` -COMMENT: - -It is guaratneed that between `m_iEndOff` and `m_iDropOff` there is only a gap -(series of empty cells). So wherever this packet lands, if it's next to -`m_iEndOff` and before `m_iDropOff` it will be the only packet that violates -the gap, hence this can be the only drop pos preceding the previous -`m_iDropOff`. - -Information returned to the caller should contain: - -1. Whether adding to the buffer was successful. - -2. Whether the "freshest" retrievable packet has been changed, that is: - * in live mode, a newly added packet has earlier delivery time than one before - * in stream mode, the newly added packet was at cell[0] - * in message mode, if the newly added packet has: - * completed the very first message - * completed any message further than first that has out-of-order flag - -The information about a changed packet is important for the caller in -live mode in order to notify the TSBPD thread. - - -WHEN CHECKING A PACKET - -1. Check the position at `m_iStartPos`. If there is a packet, return info at -its position. - -2. If position on `m_iStartPos` is empty, get the position of `m_iDropOff`. - -NOTE THAT: - - * if the buffer is empty, `m_iDropOff` and `m_iEndOff` are both 0 - - * if there is a packet in the buffer, but the first cell is empty, - then `m_iDropOff` points to this packet, while `m_iEndOff` == 0. - So after getting empty at cell 0 and `m_iDropOff` != 0, you can - read with dropping. - * If cell[0] is valid, there could be only at worst cell[1] empty - and cell[2] pointed by `m_iDropOff`. - -Note: `m_iDropOff` is updated every time a new packet arrives, even -if there are still not extracted packets in the ICR. - -3. In case of time-based checking for live mode, return empty packet info, -if this packet's play time is in the future. - -WHEN EXTRACTING A PACKET - -1. Extraction is only possible if there is a packet at cell[0]. -2. If there's no packet at cell[0], the application may request to - drop up to the given packet, or drop the whole message up to - the beginning of the next message. -3. In message mode, extraction can only extract a full message, so - if there's no full message ready, nothing is extracted. -4. When the extraction region is defined, the `m_iStartPos` is shifted - by the number of extracted packets. -5. After extracting packets, and therefore updated `m_iStartPos`, `m_iEndOff` - must be set again by searching for the first empty cell or reaching - `m_iMaxPosOff`. -6. If extraction involved dropping, `m_iDropOff` must be set again by - searching since `m_iEndOff`+1 to find the first valid packet. If - no such packet found before reaching `m_iMaxPosOff`, it's set to 0. -7. NOTE: all fields ending with `*Pos` are offsets, hence all of them must - be updated after `m_iStartPos` was changed. - - -