From 05e06668cb5334d0e4868612b3eac46bd4b72cc8 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Mon, 16 Jun 2025 10:54:04 +0200 Subject: [PATCH 01/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] [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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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. - - - From a05855b5aac6aa29291b932c265cb60274da7096 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Mon, 24 Aug 2026 16:22:39 +0200 Subject: [PATCH 27/46] (large) Improvements in scheduling (no more chain). Improvements in ofmt. Extracted and improved option utility. --- CMakeLists.txt | 5 +- apps/apputil.cpp | 204 +----- apps/apputil.hpp | 237 +----- apps/options-test.cpp | 91 +++ apps/options.cpp | 354 +++++++++ apps/options.hpp | 664 +++++++++++++++++ apps/srt-live-transmit.cpp | 6 +- apps/srt-tunnel.cpp | 6 +- apps/statswriter.cpp | 4 +- apps/support.maf | 2 + apps/transmitbase.hpp | 8 +- apps/transmitmedia.cpp | 18 +- apps/uriparser.cpp | 12 +- logging/README.md | 21 +- logging/config-model.tcl | 4 +- logging/{hvu_compat.c => hvu_compat.cpp} | 55 +- logging/hvu_compat.h | 26 +- logging/logging.cpp | 36 +- logging/logging.h | 8 +- logging/ofmt.h | 740 ++++++++++--------- logging/ofmt.md | 306 +++++--- logging/ofmt_iostream.h | 5 +- srtcore/api.cpp | 10 +- srtcore/atomic.h | 16 + srtcore/atomic_clock.h | 2 +- srtcore/atomic_msvc.h | 7 + srtcore/buffer_rcv.cpp | 6 +- srtcore/buffer_snd.cpp | 216 ++++-- srtcore/buffer_snd.h | 38 +- srtcore/channel.cpp | 22 +- srtcore/common.cpp | 6 +- srtcore/common.h | 10 +- srtcore/congctl.cpp | 13 + srtcore/congctl.h | 13 + srtcore/core.cpp | 399 ++++++----- srtcore/core.h | 194 ++--- srtcore/crypto.cpp | 2 +- srtcore/fec.cpp | 48 +- srtcore/filelist.maf | 2 +- srtcore/group.cpp | 2 +- srtcore/handshake.cpp | 6 +- srtcore/packet.h | 2 +- srtcore/packetfilter.cpp | 40 +- srtcore/queue.cpp | 48 +- srtcore/queue.h | 4 +- srtcore/schedule_snd.cpp | 349 +++++++-- srtcore/schedule_snd.h | 116 ++- srtcore/socketconfig.cpp | 12 +- srtcore/srt.h | 9 +- srtcore/srt_c_api.cpp | 1 + srtcore/sync.cpp | 65 +- srtcore/sync.h | 9 +- srtcore/utilities.h | 47 +- test/test_bonding.cpp | 15 +- test/test_buffer_snd.cpp | 81 +-- test/test_enforced_encryption.cpp | 94 +-- test/test_epoll.cpp | 8 +- test/test_reuseaddr.cpp | 2 +- test/test_sync.cpp | 18 +- testing/bstow-log.cpp | 64 ++ testing/bstow-log.hpp | 30 + testing/bstow-read.cpp | 874 +++++++++++++++++++++++ testing/bstow-read.hpp | 77 ++ testing/bstow-test/Silverball | 21 + testing/bstow-test/bstow-generate.cpp | 757 ++++++++++++++++++++ testing/bstow-test/bstow-test-src.tcl | 175 +++++ testing/bstow-test/test-packetreader.cpp | 115 +++ testing/bstow-test/ts-extract-info.tcl | 232 ++++++ testing/hvu_bigendian.h | 200 ++++++ testing/hvu_debug.h | 13 + testing/srt-test-file.cpp | 27 +- testing/srt-test-file.maf | 1 + testing/srt-test-live.cpp | 66 +- testing/srt-test-live.maf | 1 + testing/srt-test-mpbond.cpp | 2 +- testing/srt-test-mpbond.maf | 1 + testing/srt-test-multiplex.cpp | 1 + testing/srt-test-multiplex.maf | 1 + testing/srt-test-relay.cpp | 1 + testing/srt-test-relay.maf | 1 + testing/srt-test-stow.cpp | 279 ++++++++ testing/srt-test-stow.maf | 13 + testing/testmedia.cpp | 16 +- testing/testmedia.hpp | 2 +- 84 files changed, 6020 insertions(+), 1694 deletions(-) create mode 100644 apps/options-test.cpp create mode 100644 apps/options.cpp create mode 100644 apps/options.hpp rename logging/{hvu_compat.c => hvu_compat.cpp} (82%) create mode 100644 testing/bstow-log.cpp create mode 100644 testing/bstow-log.hpp create mode 100644 testing/bstow-read.cpp create mode 100644 testing/bstow-read.hpp create mode 100644 testing/bstow-test/Silverball create mode 100644 testing/bstow-test/bstow-generate.cpp create mode 100755 testing/bstow-test/bstow-test-src.tcl create mode 100644 testing/bstow-test/test-packetreader.cpp create mode 100755 testing/bstow-test/ts-extract-info.tcl create mode 100644 testing/hvu_bigendian.h create mode 100644 testing/hvu_debug.h mode change 100755 => 100644 testing/srt-test-relay.cpp create mode 100644 testing/srt-test-stow.cpp create mode 100644 testing/srt-test-stow.maf mode change 100755 => 100644 testing/testmedia.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ad04e7169..9d0483a96 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,7 @@ set1_if(DARWIN (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") OR APPLE) set1_if(GNU_OS ${CMAKE_SYSTEM_NAME} MATCHES "GNU") # Use GNU_OS to not confuse with gcc set1_if(OHOS ${CMAKE_SYSTEM_NAME} MATCHES "OHOS") set1_if(SUNOS ${SYSNAME_LC} MATCHES "sunos") -set1_if(POSIX LINUX OR DARWIN OR BSD OR SUNOS OR ANDROID OR OHOS OR (CYGWIN AND CYGWIN_USE_POSIX) OR GNU_OS) +set1_if(POSIX LINUX OR DARWIN OR BSD OR SUNOS OR ANDROID OR OHOS OR (CYGWIN AND ENABLE_CYGWIN_POSIX) OR GNU_OS) set1_if(SYMLINKABLE LINUX OR DARWIN OR BSD OR SUNOS OR CYGWIN OR GNU_OS) # "MICROSOFT" means "compiling on Microsoft Windows using Visual Studio compiler" @@ -1617,6 +1617,9 @@ if (ENABLE_APPS) srt_add_testprogram(srt-test-multiplex) srt_make_application(srt-test-multiplex) + srt_add_testprogram(srt-test-stow) + srt_make_application(srt-test-stow) + if (ENABLE_BONDING) srt_add_testprogram(srt-test-mpbond) srt_make_application(srt-test-mpbond) diff --git a/apps/apputil.cpp b/apps/apputil.cpp index 0d2623af4..812ed290c 100644 --- a/apps/apputil.cpp +++ b/apps/apputil.cpp @@ -148,7 +148,7 @@ string Join(const vector& in, string sep) if (in.empty()) return ""; - hvu::ofmtbufstream os; + hvu::ofmt_bufs os; os << in[0]; for (auto i = in.begin()+1; i != in.end(); ++i) @@ -156,208 +156,6 @@ string Join(const vector& in, string sep) return os.str(); } -// OPTION LIBRARY - -OptionScheme::Args OptionName::DetermineTypeFromHelpText(const std::string& helptext) -{ - if (helptext.empty()) - return OptionScheme::ARG_NONE; - - if (helptext[0] == '<') - { - // If the argument is , then it's ARG_NONE. - // If it's , then it's ARG_VAR. - // When closing angle bracket isn't found, fallback to ARG_ONE. - size_t pos = helptext.find('>'); - if (pos == std::string::npos) - return OptionScheme::ARG_ONE; // mistake, but acceptable - - if (pos >= 4 && helptext.substr(pos-3, 4) == "...>") - return OptionScheme::ARG_VAR; - - // We have < and > without ..., simply one argument - return OptionScheme::ARG_ONE; - } - - if (helptext[0] == '[') - { - // Argument in [] means it is optional; in this case - // you should state that the argument can be given or not. - return OptionScheme::ARG_VAR; - } - - // Also as fallback - return OptionScheme::ARG_NONE; -} - -options_t ProcessOptions(char* const* argv, int argc, std::vector scheme) -{ - using namespace std; - - string current_key; - string extra_arg; - size_t vals = 0; - OptionScheme::Args type = OptionScheme::ARG_VAR; // This is for no-option-yet or consumed - map> params; - bool moreoptions = true; - - for (char* const* p = argv+1; p != argv+argc; ++p) - { - const char* a = *p; - // cout << "*D ARG: '" << a << "'\n"; - bool isoption = false; - if (a[0] == '-') - { - isoption = true; - // If a[0] isn't NUL - because it is dash - then - // we can safely check a[1]. - // An expression starting with a dash is not - // an option marker if it is a single dash or - // a negative number. - if (!a[1] || isdigit(a[1])) - isoption = false; - } - - if (moreoptions && isoption) - { - bool arg_specified = false; - size_t seppos; // (see goto, it would jump over initialization) - current_key = a+1; - if ( current_key == "-" ) - { - // The -- argument terminates the options. - // The default key is restored to empty so that - // it collects now all arguments under the empty key - // (not-option-assigned argument). - moreoptions = false; - goto EndOfArgs; - } - - // Maintain the backward compatibility with argument specified after : - // or with one string separated by space inside. - seppos = current_key.find(':'); - if (seppos == string::npos) - seppos = current_key.find(' '); - if (seppos != string::npos) - { - // Old option specification. - extra_arg = current_key.substr(seppos + 1); - current_key = current_key.substr(0, 0 + seppos); - arg_specified = true; // Prevent eating args from option list - } - - params[current_key].clear(); - vals = 0; - - if (extra_arg != "") - { - params[current_key].push_back(extra_arg); - ++vals; - extra_arg.clear(); - } - - // Find the key in the scheme. If not found, treat it as ARG_NONE. - for (const auto& s: scheme) - { - if (s.names().count(current_key)) - { - // cout << "*D found '" << current_key << "' in scheme type=" << int(s.type) << endl; - // If argument was specified using the old way, like - // -v:0 or "-v 0", then consider the argument specified and - // treat further arguments as either no-option arguments or - // new options. - if (s.type == OptionScheme::ARG_NONE || arg_specified) - { - // Anyway, consider it already processed. - break; - } - type = s.type; - - if ( vals == 1 && type == OptionScheme::ARG_ONE ) - { - // Argument for one-arg option already consumed, - // so set to free args. - goto EndOfArgs; - } - goto Found; - } - - } - // Not found: set ARG_NONE. - // cout << "*D KEY '" << current_key << "' assumed type NONE\n"; -EndOfArgs: - type = OptionScheme::ARG_VAR; - current_key = ""; -Found: - continue; - } - - // Collected a value - check if full - // cout << "*D COLLECTING '" << a << "' for key '" << current_key << "' (" << vals << " so far)\n"; - params[current_key].push_back(a); - ++vals; - if ( vals == 1 && type == OptionScheme::ARG_ONE ) - { - // cout << "*D KEY TYPE ONE - resetting to empty key\n"; - // Reset the key to "default one". - current_key = ""; - vals = 0; - type = OptionScheme::ARG_VAR; - } - else - { - // cout << "*D KEY type VAR - still collecting until the end of options or next option.\n"; - } - } - - return params; -} - -string OptionHelpItem(const OptionName& o) -{ - string out = "\t-" + o.main_name; - string hlp = o.helptext; - string prefix; - - if (hlp == "") - { - hlp = " (Undocumented)"; - } - else if (hlp[0] != ' ') - { - size_t end = string::npos; - if (hlp[0] == '<') - { - end = hlp.find('>'); - } - else if (hlp[0] == '[') - { - end = hlp.find(']'); - } - - if (end != string::npos) - { - ++end; - } - else - { - end = hlp.find(' '); - } - - if (end != string::npos) - { - prefix = hlp.substr(0, end); - //while (hlp[end] == ' ') - // ++end; - hlp = hlp.substr(end); - out += " " + prefix; - } - } - - out += " -" + hlp; - return out; -} - const char* SRTClockTypeStr() { const int clock_type = srt_clock_type(); diff --git a/apps/apputil.hpp b/apps/apputil.hpp index 9242f0c36..96a37fbac 100644 --- a/apps/apputil.hpp +++ b/apps/apputil.hpp @@ -21,6 +21,12 @@ #include "netinet_any.h" #include "utilities.h" #include "srt.h" +#include "options.hpp" + +namespace srt +{ + using namespace hvu; +} #if _WIN32 @@ -103,237 +109,6 @@ template OnReturnSetter OnReturnSet(VarType& target, ValType v) { return OnReturnSetter(target, v); } -// ---- OPTIONS MODULE - -inline bool CheckTrue(const std::vector& in) -{ - if (in.empty()) - return true; - - const std::set false_vals = { "0", "no", "off", "false" }; - if (false_vals.count(in[0])) - return false; - - return true; - - //if (in[0] != "false" && in[0] != "off") - // return true; - - //return false; -} - -template -static inline Number StrToNumber(const std::string& ) -{ - typename Number::incorrect_version wrong = Number::incorrect_version; - return Number(); -} - -#define STON(type, function) \ -template<> inline type StrToNumber(const std::string& s) { return function (s, 0, 0); } - -STON(int, stoi); -STON(unsigned long, stoul); -STON(unsigned int, stoul); -STON(long long, stoll); -STON(unsigned long long, stoull); - -#undef STON - -typedef std::map> options_t; - -struct OutList -{ - typedef std::vector type; - static type process(const options_t::mapped_type& i) { return i; } -}; - -struct OutString -{ - typedef std::string type; - static type process(const options_t::mapped_type& i) { return Join(i, " "); } -}; - -struct NumberAutoConvert -{ - std::string value; - - NumberAutoConvert(): NumberAutoConvert("") {} - NumberAutoConvert(const std::string& arg): NumberAutoConvert(arg.c_str()) {} - NumberAutoConvert(const char* arg): value(arg) - { - if (value.empty()) - value = "0"; // Must convert to a default 0 number - } - - template - operator Number() - { - return StrToNumber(value); - } -}; - -struct OutNumber -{ - typedef NumberAutoConvert type; - static type process(const options_t::mapped_type& i) - { - // Numbers can't be joined, use the "last overrides" rule. - if (i.empty()) - return {"0"}; - - return type { i.back() }; - } -}; - -template -struct OutNumberAs -{ - typedef Number type; - static type process(const options_t::mapped_type& i) - { - return OutNumber::process(i); - } -}; - - -struct OutBool -{ - typedef bool type; - static type process(const options_t::mapped_type& i) { return CheckTrue(i); } -}; - -struct OptionName; - -struct OptionScheme -{ - const OptionName* pid; - enum Args { ARG_NONE, ARG_ONE, ARG_VAR } type; - - OptionScheme(const OptionScheme&) = default; - OptionScheme(OptionScheme&& src) - : pid(src.pid) - , type(src.type) - { - } - - OptionScheme(const OptionName& id, Args tp); - - const std::set& names() const; -}; - -struct OptionName -{ - std::string helptext; - std::string main_name; - std::set names; - - template - OptionName(std::string ht, std::string first, Args... rest) - : helptext(ht), main_name(first), - names {first, rest...} - { - } - - template - OptionName(std::vector& sc, OptionScheme::Args type, - std::string ht, std::string first, Args... rest) - : helptext(ht), main_name(first), - names {first, rest...} - { - sc.push_back(OptionScheme(*this, type)); - } - - template - OptionName(std::vector& sc, - std::string ht, std::string first, Args... rest) - : helptext(ht), main_name(first), - names {first, rest...} - { - OptionScheme::Args type = DetermineTypeFromHelpText(ht); - sc.push_back(OptionScheme(*this, type)); - } - - OptionName(std::initializer_list args): main_name(*args.begin()), names(args) {} - - operator std::set() { return names; } - operator const std::set() const { return names; } - -private: - static OptionScheme::Args DetermineTypeFromHelpText(const std::string& helptext); -}; - -inline OptionScheme::OptionScheme(const OptionName& id, Args tp): pid(&id), type(tp) {} -inline const std::set& OptionScheme::names() const { return pid->names; } - -template inline -typename OutType::type Option(const options_t&, OutValue deflt=OutValue()) { return deflt; } - -template inline -typename OutType::type Option(const options_t& options, OutValue deflt, std::string key, Args... further_keys) -{ - auto i = options.find(key); - if ( i == options.end() ) - return Option(options, deflt, further_keys...); - return OutType::process(i->second); -} - -template -struct OptionTrapType -{ - static TrapType pass(TrapType v) { return v; } -}; - -template<> -struct OptionTrapType -{ - static std::string pass(const char* v) { return v; } -}; - -template inline -typename OutType::type Option(const options_t& options, OutValue deflt, const OptionName& oname) -{ - (void)OptionTrapType::pass(deflt); - for (auto key: oname.names) - { - auto i = options.find(key); - if ( i != options.end() ) - { - return OutType::process(i->second); - } - } - return deflt; -} - -template inline -typename OutType::type Option(const options_t& options, const OptionName& oname) -{ - typedef typename OutType::type out_t; - for (auto key: oname.names) - { - auto i = options.find(key); - if ( i != options.end() ) - { - return OutType::process(i->second); - } - } - return out_t(); -} - -inline bool OptionPresent(const options_t& options, const std::set& keys) -{ - for (auto key: keys) - { - auto i = options.find(key); - if ( i != options.end() ) - return true; - } - return false; -} - -options_t ProcessOptions(char* const* argv, int argc, std::vector scheme); -std::string OptionHelpItem(const OptionName& o); - const char* SRTClockTypeStr(); void PrintLibVersion(); bool IsTargetAddrSelf(const sockaddr* boundaddr, const sockaddr* targetaddr); diff --git a/apps/options-test.cpp b/apps/options-test.cpp new file mode 100644 index 000000000..64fe5d65a --- /dev/null +++ b/apps/options-test.cpp @@ -0,0 +1,91 @@ + +// NOTE: DEMONSTRATION purposes only. + +#include "../logging/ofmt_iostream.h" +#include + +#include "options.hpp" + +using namespace hvu; +using namespace std; + +int main( int argc, char** argv ) +{ + OptionHandler optargs; + + OptionName + o_timeout ((optargs), " Data transmission timeout", "t", "to", "timeout" ), + o_logfa ((optargs), " Enabled Functional Areas (see --help logging)", "lfa", "logfa"), + o_verbose ((optargs), "[channel=0|1|./file] Print size of every packet transferred on stdout or specified [channel]", "v", "verbose"), + o_quiet ((optargs), " Set quiet mode", "q", "quiet"), + o_coord ((optargs), OptionScheme::ARG_FIXED(2), " Coordinates", "c", "coord"), + o_help ((optargs), "[special=logging] This help", "?", "help", "-help") + ; + + OptionStatus ost = optargs.process(argv, argc); + + if (!ost) + { + ofprintl(cout, "ERROR: ", ost.error_code_str(), ": ", ost.error_option); + } + + bool need_help = false; + try + { + need_help = optargs.exists(o_help); + + vector logfas = optargs.get(o_logfa); + + string helpspec = optargs.get(o_help); + + string verbspec = optargs.get(o_verbose); + + int timeout = optargs.get(o_timeout, -1); + + ofprintl(cout, "Need help: ", fmt_if(need_help, "yes", "no"), " spec: ", helpspec); + ofprintl(cout, "Verbose: ", verbspec, " (", fmt_if(optargs.exists(o_verbose), "wanted", "unwanted"), ")"); + + ofprint(cout, "Log FAs: "); + if (logfas.empty()) + ofprint(cout, "(empty)"); + else + { + ofprint(cout, logfas[0]); + for (size_t i = 1; i < logfas.size(); ++i) + ofprint(cout, ", ", logfas[i]); + } + ofprintl(cout); + + ofprintl(cout, "Timeout: ", timeout); + + vector coords = optargs.get(o_coord); + ofprintl(cout, "Coordinates: (", coords[0], " ", coords[1], ")"); + + } + catch (std::exception& e) + { + ofprintl(cout, "EXCEPTION: ", e.what()); + } + + if (need_help) + { + ofprintl(cout, "WITH NEED HELP, here is the option help:"); + for (auto& option: optargs.options()) + { + //ofprintl(cout, " -", fmt(option.name(), fmtc().left().width(10)), option.helptext()); + ofprintl(cout, option.helpitem()); + } + } + + auto& unknown = optargs.unknown(); + if (!unknown.empty()) + { + ofprint(cout, "Unrecognized options: "); + for (auto& o: unknown) + ofprint(cout, o, " "); + ofprintl(cout); + } + + return 0; +} + diff --git a/apps/options.cpp b/apps/options.cpp new file mode 100644 index 000000000..79dffa5fb --- /dev/null +++ b/apps/options.cpp @@ -0,0 +1,354 @@ +#include "options.hpp" +#include "../logging/ofmt.h" + +namespace hvu +{ +OptionScheme::Args OptionName::DetermineTypeFromHelpText(const std::string& helptext) +{ + if (helptext.empty()) + return OptionScheme::ARG_NONE; + + if (helptext[0] == '<') + { + // If the argument is , then it's ARG_NONE. + // If it's , then it's ARG_VAR. + // When closing angle bracket isn't found, fallback to ARG_ONE. + size_t pos = helptext.find('>'); + if (pos == std::string::npos) + return OptionScheme::ARG_ONE; // mistake, but acceptable + + if (pos >= 4 && helptext.substr(pos-3, 4) == "...>") + return OptionScheme::ARG_VAR; + + // We have < and > without ..., simply one argument + return OptionScheme::ARG_ONE; + } + + if (helptext[0] == '[') + { + // Argument in [] means it is optional; in this case + // you should state that the argument can be given or not. + return OptionScheme::ARG_VAR; + } + + // Also as fallback + return OptionScheme::ARG_NONE; +} + +#ifdef HVU_OPTIONS_ENABLE_DEBUG +#define ODEBUG(...) ofprintl(std::cerr, __VA_ARGS___) +std::string g_debug_arg [3] = { "NONE", "ONE", "VAR" }; +#else +#define ODEBUG(...) +#endif + +// XXX Required: +// +// Remove this function as API; required will be processing +// using fields of ProcessHandler +// +// Add a feature: bool grouped_single. If true, then: +// - if the scheme isn't empty +// - if the option name wasn't found in the scheme +// - if the option doesn't start with dash +// : Split the name into single characters and review +// them all as if they were multiple options; also +// if any of them grabs arguments, expect arguments +// for it in next words + +OptionStatus ProcessOptions(const char* const* argv, int argc, +// map>& w_params, + options_t& w_params, + const std::vector& scheme, + OptionScheme::Args default_scheme, + std::string separators, + std::set* pw_unknown) +{ + using namespace std; + + string current_key; + vector extra_args; + size_t vals = 0; + OptionScheme::Args type = OptionScheme::ARG_VAR; // This is for no-option-yet or consumed + bool moreoptions = true; + map found_schemes; + + for (const char* const* p = argv+1; p != argv+argc; ++p) + { + const char* a = *p; + ODEBUG("*D ARG: '", a, "'"); + bool isoption = false; + if (a[0] == '-') + { + isoption = true; + // If a[0] isn't NUL - because it is dash - then + // we can safely check a[1]. + // An expression starting with a dash is not + // an option marker if it is a single dash or + // a negative number. + if (!a[1] || isdigit(a[1])) + { + ODEBUG("*D NOT OPTION"); + isoption = false; + } + } + + if (moreoptions && isoption) + { + bool arg_specified = false; + size_t seppos = std::string::npos; // (see goto, it would jump over initialization) + char argsep = ':'; // needed when separator detected + current_key = a+1; + if (current_key == "-") + { + // The -- argument terminates the options. + // The default key is restored to empty so that + // it collects now all arguments under the empty key + // (not-option-assigned argument). + moreoptions = false; + goto EndOfArgs; + } + + if (current_key == "/") + { + // Argument termination tag: -/ + // This can be used for terminating the argument list + // for a scheme type ARG_VAR; turns the current key + // back to empty so that next arguments belong to free ones. + + // XXX Consider handling cases like -a list of args -/a others + // in order to specify the option name that was last open; + // it requires some more robust error handling, however. + goto EndOfArgs; + } + + // Alternative ways to specify arguments for options basing + // on a special character found in the alleged option-spec + for (char sep: separators) + { + seppos = current_key.find(sep); + if (seppos != string::npos) + { + argsep = sep; + break; + } + } + + if (seppos != string::npos) + { + // Old option specification. + string extra_arg = current_key.substr(seppos + 1); + current_key = current_key.substr(0, 0 + seppos); + arg_specified = true; // Prevent eating args from option list + ODEBUG("*D Consumed arg '", extra_arg, "' for option -", current_key); + + for (;;) + { + // At start, assume that the argument starts + // where extra_arg begins, but it may contain + // further positions + seppos = extra_arg.find(argsep); + extra_args.push_back(extra_arg.substr(0, seppos)); + if (seppos == string::npos) + break; + extra_arg = extra_arg.substr(seppos+1); + } + + // But then, continue the same argument search for the next ones. + } + + w_params[current_key].clear(); + vals = 0; + + if (!extra_args.empty()) + { + auto& target = w_params[current_key]; + vals += extra_args.size(); + target.insert(target.end(), + make_move_iterator(extra_args.begin()), + make_move_iterator(extra_args.end())); + + extra_args.clear(); + } + + if (!scheme.empty()) + { + // Find the key in the scheme. If not found, treat it as ARG_NONE. + for (const auto& s: scheme) + { + if (s.names().count(current_key)) + { + found_schemes[current_key] = &s; + ODEBUG("*D found '", current_key, "' in scheme type=", int(s.type)); + // If argument was specified using the old way, like + // -v:0 or "-v 0", then consider the argument specified and + // treat further arguments as either no-option arguments or + // new options. + if (s.type == OptionScheme::ARG_NONE || arg_specified) + { + // Anyway, consider it already processed. + goto EndOfArgs; + } + type = s.type; + + if (vals == type.maxargs()) + //if (vals == 1 && type == OptionScheme::ARG_ONE) + { + // Argument for one-arg option already consumed, + // so set to free args. + goto EndOfArgs; + } + goto Found; + } + + } + ODEBUG("*D NOTIFYING UNKNOWN KEY: -", current_key); + // Insert it if unknown bypass is provided and there + // are any options defined. In case of "free specification" + // this isn't done because this time "any option is unknown" + if (pw_unknown) + pw_unknown->insert(current_key); + } + // Not found: set ARG_NONE. + ODEBUG("*D KEY '", current_key, "' assumed type ", g_debug_arg[default_scheme]); + if (default_scheme != OptionScheme::ARG_NONE) + { + type = default_scheme; + continue; + } +EndOfArgs: + // Setting empty-string key assumes that the further + // non-option arguments belong to "free arguments", and + // its scheme is "var". + type = OptionScheme::ARG_VAR; + current_key = ""; +Found: + continue; + } + + // Collected a value - check if full + ODEBUG("*D COLLECTING '", a, "' for key '", current_key, "' (", vals, " so far)"); + w_params[current_key].push_back(a); + ++vals; + if (vals == type.maxargs()) + //if ( vals == 1 && type == OptionScheme::ARG_ONE ) + { + ODEBUG("*D KEY TYPE ONE - resetting to empty key"); + // Reset the key to "default one". + current_key = ""; + vals = 0; + type = OptionScheme::ARG_VAR; + } + else + { + ODEBUG("*D KEY type VAR - still collecting until the end of options or next option."); + } + } + + // Post-verification + if (!scheme.empty()) + { + // Find the key in the scheme. If not found, treat it as ARG_NONE. + for (const auto& a: w_params) + { + if (a.first == "") // discard free argumetns + continue; + + auto it = found_schemes.find(a.first); + if (it == found_schemes.end()) + { + // Report error: option not found in the scheme + return OptionStatus::error(a.first, OptionStatus::ERR_UNDEF); + } + + const OptionScheme& s = *it->second; + if (s.type.n == 0) + { + if (!a.second.empty()) + return OptionStatus::error(a.first, OptionStatus::ERR_EXCEED); + } + else if (s.type.n > 0) + { + // The number of arguments must be equal + int dif = s.type.n - int(a.second.size()); + if (dif > 0) + return OptionStatus::error(a.first, OptionStatus::ERR_MISSING); + else if (dif < 0) + return OptionStatus::error(a.first, OptionStatus::ERR_EXCEED); + } + else // < 0 + { + // This means optional so only check if exceeded the maximum + size_t nexpect = -s.type.n - 1; + if (a.second.size() > nexpect) + return OptionStatus::error(a.first, OptionStatus::ERR_EXCEED); + } + } + } + + return OptionStatus::success(); +} + +std::string OptionHelpItem(const OptionName& o, const std::string& px, size_t width, char postfill) +{ + using namespace std; + + ofmt_bufs ospec; + + ospec << px << "-" << o.main_name; + string hlp = o.helptext; + string prefix; + + if (hlp == "") + { + hlp = " (Undocumented)"; + } + else if (hlp[0] != ' ') + { + size_t end = string::npos; + if (hlp[0] == '<') + { + end = hlp.find('>'); + } + else if (hlp[0] == '[') + { + end = hlp.find(']'); + } + + if (end != string::npos) + { + ++end; + } + else + { + end = hlp.find(' '); + } + + if (end != string::npos) + { + prefix = hlp.substr(0, end); + //while (hlp[end] == ' ') + // ++end; + hlp = hlp.substr(end); + ospec << " " << prefix; + } + } + + return ofcat( fmt(ospec.str(), fmtc().left().fill(postfill).width(width)), "-", hlp); +} + +const char* OptionStatus::error_code_str() const +{ + static const char* const s_codes [] = { + "success", + "missing arguments", + "too many arguments", + "unknown option" + }; + + if (int(error_code) < 0 || int(error_code) > ERR_UNDEF) + return "UNKNOWN ERROR"; + return s_codes[error_code]; +} + +} diff --git a/apps/options.hpp b/apps/options.hpp new file mode 100644 index 000000000..d3c13d465 --- /dev/null +++ b/apps/options.hpp @@ -0,0 +1,664 @@ + +#ifndef INC_HVU_OPTIONS_H +#define INC_HVU_OPTIONS_H + +#include +#include +#include +#include +#include + +namespace hvu +{ + +// XXX Utility; move somewhere else + + +struct NumberError: public std::invalid_argument +{ + std::string msg; + + NumberError(const std::string& optname): std::invalid_argument("stoi") + { + msg = "Number conversion error for -" + optname; + } + + const char* what() const noexcept override + { + return msg.c_str(); + } +}; + +inline bool CheckTrue(const std::vector& in, bool ifempty = true) +{ + if (in.empty()) + return ifempty; + + const std::set false_vals = { "0", "no", "off", "false" }; + if (false_vals.count(in[0])) + return false; + + return true; +} + +template +struct NumberConvertFwd +{ + static Type convert(const Input& s) + { + typename Type::incorrect_version wrong = Type::incorrect_version; + return Type(); + } +}; + +template +static inline Number StrToNumber(const Input& s) +{ + return NumberConvertFwd::convert(s); +} + +template +struct NumberConvertFwd> +{ + static Number convert(const std::vector& sv) + { + std::string single; + if (sv.empty()) + return StrToNumber(std::string("0")); + return StrToNumber(sv.back()); + } +}; + +#define STON(type, function) \ +template<> inline type StrToNumber(const std::string& s) { return function (s, 0, 0); } + +STON(int, stoi); +STON(unsigned long, stoul); +STON(unsigned int, stoul); +STON(long long, stoll); +STON(unsigned long long, stoull); + +#undef STON + +template +struct NumberConvertFwd, std::vector> +{ + static std::vector convert(const std::vector& sv) + { + std::vector out; + for (auto& s: sv) + out.push_back(StrToNumber(s)); + return out; + } +}; + +typedef std::map> options_t; + +struct OutList +{ + typedef std::vector type; + static type process(const options_t::mapped_type& i) { return i; } +}; + +struct OutString +{ + typedef std::string type; + static type process(const options_t::mapped_type& i) + { + if (i.empty()) + return type(); + + size_t total = 0; + for (auto& s: i) + total += s.size(); + if (total == 0) + return type(); + + type out; + out.reserve(total + i.size() + 2); + out = i[0]; + for (size_t y = 1; y < i.size(); ++y) + { + out += " "; + out += i[y]; + } + + return out; + } +}; + +struct NumberAutoConvert +{ + options_t::mapped_type value; + + NumberAutoConvert(const options_t::mapped_type& args): value(args) + { + } + + // XXX Required for old API with Option calls. + // If so, the value type can be changed to const reference. + NumberAutoConvert() {} + NumberAutoConvert(const char* spec) + { + value.push_back(spec); + } + + template + operator Number() + { + return StrToNumber(value); + } +}; + +struct OutNumber +{ + typedef NumberAutoConvert type; + static type process(const options_t::mapped_type& i) + { + return type(i); + } +}; + +template +struct OutNumberAs +{ + typedef Number type; + static type process(const options_t::mapped_type& i) + { + return OutNumber::process(i); + } +}; + + +struct OutBool +{ + typedef bool type; + static type process(const options_t::mapped_type& i) { return CheckTrue(i); } +}; + +struct OptionName; + +struct OptionArgSpec +{ + int n; + + bool operator==(const OptionArgSpec& o) const { return n == o.n; } + bool operator!=(const OptionArgSpec& o) const { return n != o.n; } + + size_t maxargs() const + { + if (n < 0) + return -n - 1; + return n; + } +}; + +struct OptionScheme +{ + const OptionName* pid; + + typedef OptionArgSpec Args; + + Args type; + + constexpr static void checkPositive(int n) + { + if (n < 1) + throw std::invalid_argument("Value expected >= 1"); + } + + static constexpr Args ARG_NONE = Args { 0 }; + static constexpr Args ARG_ONE = Args { 1 }; + static constexpr Args ARG_VAR = Args { -1 }; + static constexpr Args ARG_FIXED(int n) { checkPositive(n); return Args { n }; } + static constexpr Args ARG_OPT(int n) { checkPositive(n); return Args { -n-1 }; } + + OptionScheme(const OptionScheme&) = default; + OptionScheme(OptionScheme&& src) + : pid(src.pid) + , type(src.type) + { + } + + OptionScheme(const OptionName& id, Args tp); + + const std::set& names() const; + const std::string& helptext() const; + const std::string& name() const; + std::string helpitem() const; +}; + +class OptionHandler; + +struct OptionName +{ + std::string helptext; + std::string main_name; + std::set names; + + // XXX Check it this constructor is still usable. + // Likely so, as long as you define the OptionScheme table separately. + template + OptionName(std::string ht, std::string first, Args... rest) + : helptext(ht), main_name(first), + names {first, rest...} + { + } + OptionName(std::initializer_list args): main_name(*args.begin()), names(args) {} + + template + OptionName(std::vector& sc, OptionScheme::Args type, + std::string ht, std::string first, Args... rest) + : helptext(ht), main_name(first), + names {first, rest...} + { + sc.push_back(OptionScheme(*this, type)); + } + + template + OptionName(std::vector& sc, + std::string ht, std::string first, Args... rest) + : helptext(ht), main_name(first), + names {first, rest...} + { + OptionScheme::Args type = DetermineTypeFromHelpText(ht); + sc.push_back(OptionScheme(*this, type)); + } + + template + OptionName(OptionHandler& sc, OptionScheme::Args type, + std::string ht, std::string first, Args... rest); + + template + OptionName(OptionHandler& sc, + std::string ht, std::string first, Args... rest); + + operator std::set() { return names; } + operator const std::set() const { return names; } + +private: + static OptionScheme::Args DetermineTypeFromHelpText(const std::string& helptext); +}; + +std::string OptionHelpItem(const OptionName& o, + const std::string& px = "\t", + size_t width = 24, + char postfill = ' '); + +inline OptionScheme::OptionScheme(const OptionName& id, Args tp): pid(&id), type(tp) {} +inline const std::set& OptionScheme::names() const { return pid->names; } +inline const std::string& OptionScheme::helptext() const { return pid->helptext; } +inline const std::string& OptionScheme::name() const { return pid->main_name; } +inline std::string OptionScheme::helpitem() const { return OptionHelpItem(*pid); } + +#ifndef HVU_OPTIONS_USE_OLD_API + +template inline +typename OutType::type Option(const options_t&, OutValue deflt=OutValue()) { return deflt; } + +template inline +typename OutType::type Option(const options_t& options, OutValue deflt, std::string key, Args... further_keys) +{ + auto i = options.find(key); + if ( i == options.end() ) + return Option(options, deflt, further_keys...); + return OutType::process(i->second); +} + +template +struct OptionTrapType +{ + typedef TrapType type_t; + static TrapType pass(TrapType v) { return v; } +}; + +template<> +struct OptionTrapType +{ + typedef std::string type_t; + static std::string pass(const char* v) { return v; } +}; + +template inline +typename OutType::type Option(const options_t& options, OutValue deflt, const OptionName& oname) +{ + (void)OptionTrapType::pass(deflt); + for (auto key: oname.names) + { + auto i = options.find(key); + if ( i != options.end() ) + { + return OutType::process(i->second); + } + } + return deflt; +} + +template inline +typename OutType::type Option(const options_t& options, const OptionName& oname) +{ + typedef typename OutType::type out_t; + for (auto key: oname.names) + { + auto i = options.find(key); + if ( i != options.end() ) + { + return OutType::process(i->second); + } + } + return out_t(); +} + +inline bool OptionPresent(const options_t& options, const std::set& keys) +{ + for (auto key: keys) + { + auto i = options.find(key); + if ( i != options.end() ) + return true; + } + return false; +} + +#endif + +struct OptionStatus +{ + bool status; + std::string error_option; + + enum Error + { + ERR_NONE = 0, + ERR_MISSING = 1, + ERR_EXCEED = 2, + ERR_UNDEF = 3 + } error_code; + + OptionStatus(bool st, const std::string& s, Error e): + status(st), error_option(s), error_code(e) + { + } + + operator bool() const { return status; } + + static OptionStatus success() { return { true, "", ERR_NONE }; } + static OptionStatus error(const std::string& optname, Error code) + { + return { false, optname, code }; + } + + const char* error_code_str() const; + +private: + OptionStatus() {} +}; + +OptionStatus ProcessOptions(const char* const* argv, int argc, options_t& out, + const std::vector& scheme, + OptionScheme::Args default_scheme = OptionScheme::ARG_NONE, + std::string separators = std::string(), + std::set* punknown = nullptr); + +// OLD API +inline options_t ProcessOptions(const char* const* argv, int argc, + const std::vector& scheme, + OptionScheme::Args default_scheme = OptionScheme::ARG_NONE) +{ + options_t params; + ProcessOptions(argv, argc, (params), scheme, default_scheme); + return params; +} + +struct OptionProxy +{ + typedef options_t::mapped_type value_t; + std::string option; + value_t value; + bool found; + + // Universal is to treat it as number + template TargetType as(); + + template + operator TargetType () + { + if (!found) + return TargetType(); + + try + { + return as(); + } + catch (std::invalid_argument& cve) + { + throw NumberError(option); + } + } +}; + +template inline +TargetType OptionProxy::as() +{ + return OutNumberAs::process(value); +} + + +// Specializations are for bool, string, vector +template<> inline +bool OptionProxy::as() +{ + return OutBool::process(value); +} + +template<> inline +std::string OptionProxy::as() +{ + return OutString::process(value); +} + +template<> inline +std::vector OptionProxy::as() +{ + return OutList::process(value); +} + +template +struct OptionProxyDef +{ + using value_t = OptionProxy::value_t; + OptionProxy derived; + OutValue deflt; + + OptionProxyDef(const std::string& name, const value_t& val, bool isexplicit /*= true*/): derived {name, val, isexplicit} {} + OptionProxyDef(const std::string& name, const OutValue& val, bool isexplicit /*= false*/): derived {name, value_t(), isexplicit}, deflt(val) {} + + template + operator TargetType () + { + if (!derived.found) + return TargetType(deflt); + + return derived.as(); + } +}; + +template<> +struct OptionProxyDef +{ + using value_t = OptionProxy::value_t; + OptionProxy derived; + value_t deflt; + + OptionProxyDef(const std::string& name, const value_t& val, bool isexplicit): derived {name, isexplicit ? val : value_t(), isexplicit}, deflt(isexplicit ? value_t() : val) {} + + template + operator TargetType () + { + if (!derived.found) + return TargetType(deflt); + + return derived.as(); + } +}; + + +class OptionHandler +{ + typedef options_t::mapped_type value_t; + typedef options_t params_t; + std::vector m_optargs; + options_t m_params; + std::set m_unknown; + OptionScheme::Args m_default_arg = OptionScheme::ARG_NONE; + std::string m_sepchars = ": ,"; + +public: + + OptionHandler() {} + + void advice(OptionName& tag, const OptionScheme::Args& type) + { + m_optargs.push_back(OptionScheme(tag, type)); + } + + // PROPERTY:RW + OptionScheme::Args default_arg() const { return m_default_arg; } + void default_arg(OptionScheme::Args a) { m_default_arg = a; } + + // PROPERTY:RO + const std::set& unknown() const { return m_unknown; } + + // PROPERTY:WO + void separators(const std::string& seps) { m_sepchars = seps; } + + // PROPERTY:RO + const params_t& params() const { return m_params; } + + OptionStatus process(const char* const* argv, int argc) + { + return ProcessOptions(argv, argc, (m_params), m_optargs, m_default_arg, m_sepchars, &m_unknown); + } + + /* XXX ProcessOptions not implemented to handle this; + may be added in the future. + void process(const std::vector& args) + { + m_params = ProcessOptions(args); + } + */ + + typedef OptionProxy Proxy; + + template using ProxyDef = OptionProxyDef; + + bool exists(const OptionName& oname) const + { + return OptionPresent(m_params, oname); + } + + template + ProxyDef::type_t> get(const OptionName& oname, OutValue deflt) const + { + (void)OptionTrapType::pass(deflt); + typedef typename OptionTrapType::type_t RealType; + for (auto key: oname.names) + { + auto i = m_params.find(key); + if ( i != m_params.end() ) + { + return ProxyDef (key, i->second, true); + } + } + return ProxyDef (oname.main_name, RealType(deflt), false); + } + + template + ProxyDef::type_t> getname(OutValue deflt = OutValue()) + { + typedef typename OptionTrapType::type_t RealType; + return ProxyDef ("", RealType(deflt), false); + } + + template + ProxyDef::type_t> getname(OutValue deflt, const std::string& key, const Args&... further_keys) + { + typedef typename OptionTrapType::type_t RealType; + auto i = m_params.find(key); + if ( i == m_params.end() ) + return getname(deflt, further_keys...); + return ProxyDef (i->first, i->second, true); + } + + template + ProxyDef::type_t> operator()(OutValue deflt, const std::string& key, const Args&... further_keys) + { + return getname(deflt, key, further_keys...); + } + + Proxy getname1(const std::string& key) const + { + auto i = m_params.find(key); + if ( i == m_params.end() ) + return Proxy { key, value_t(), false }; + return Proxy {key, i->second, true}; + } + + Proxy operator[](const std::string& name) const + { + return getname1(name); + } + + + Proxy get(const OptionName& oname) const + { + for (auto key: oname.names) + { + auto i = m_params.find(key); + if ( i != m_params.end() ) + { + return Proxy {key, i->second, true}; + } + } + return Proxy {oname.main_name, value_t(), false }; + } + + Proxy operator[](const OptionName& oname) const + { + return get(oname); + } + + const std::vector& options() const + { + return m_optargs; + } +}; + +template inline +OptionName::OptionName(OptionHandler& sc, OptionScheme::Args type, + std::string ht, std::string first, Args... rest): + helptext(ht), + main_name(first), + names {first, rest...} +{ + sc.advice(*this, type); +} + + template inline +OptionName::OptionName(OptionHandler& sc, + std::string ht, std::string first, Args... rest): + helptext(ht), + main_name(first), + names {first, rest...} +{ + OptionScheme::Args type = DetermineTypeFromHelpText(ht); + sc.advice(*this, type); +} + + + + +} + + +#endif diff --git a/apps/srt-live-transmit.cpp b/apps/srt-live-transmit.cpp index 10b9e99f7..10f4b9d1c 100644 --- a/apps/srt-live-transmit.cpp +++ b/apps/srt-live-transmit.cpp @@ -80,7 +80,7 @@ #include // because contains the declaration of logger_config using namespace std; - +using namespace srt; struct ForcedExit: public std::runtime_error @@ -839,7 +839,7 @@ int main(int argc, char** argv) { lostBytes += pkt->payload.size(); } - else if (!tar->Write(pkt->payload.data(), pkt->payload.size(), cfg.srctime ? pkt->time : 0, out_stats)) + else if (!tar->Write(pkt->payload.data(), pkt->payload.size(), cfg.srctime ? pkt->time_us : 0, out_stats)) { lostBytes += pkt->payload.size(); } @@ -893,7 +893,7 @@ void TestLogHandler(void* opaque, int level, const char* file, int line, const c time_t now; time(&now); - struct tm local = hvu::SysLocalTime(now); + struct tm local = hvu::sys_localtime(now); cerr << "[" << std::put_time(&local, "%c") << " " << file << ":" << line << "(" << area << ")]{" << level << "} " << prefix << message diff --git a/apps/srt-tunnel.cpp b/apps/srt-tunnel.cpp index 8e008f69c..b5d646dea 100644 --- a/apps/srt-tunnel.cpp +++ b/apps/srt-tunnel.cpp @@ -468,12 +468,12 @@ class SrtMedium: public Medium using namespace hvu; string message = srt_strerror(srterror, errnov); - string hm = fmtcat("ERROR #", srterror, ": ", text, ": ", message); + string hm = ofcat("ERROR #", srterror, ": ", text, ": ", message); if (errnov == 0) throw TransmissionError(hm); else { - throw TransmissionError(fmtcat(hm, ": #", errnov, ": ", SysStrError(errnov))); + throw TransmissionError(ofcat(hm, ": #", errnov, ": ", sys_strerror(errnov))); } } @@ -567,7 +567,7 @@ class TcpMedium: public Medium static void Error(int verrno, const string& text) { - throw TransmissionError("ERROR: " + text + ": " + hvu::SysStrError(verrno)); + throw TransmissionError("ERROR: " + text + ": " + hvu::sys_strerror(verrno)); } virtual ~TcpMedium() diff --git a/apps/statswriter.cpp b/apps/statswriter.cpp index 7eab5dc9b..4e54b87d4 100644 --- a/apps/statswriter.cpp +++ b/apps/statswriter.cpp @@ -108,10 +108,10 @@ std::string SrtStatsWriter::print_timestamp() const auto systime_now = system_clock::now(); const time_t time_now = system_clock::to_time_t(systime_now); - ofmtbufstream output; + ofmt_bufs output; // SysLocalTime returns zeroed tm_now on failure, which is ok for put_time. - const tm tm_now = SysLocalTime(time_now); + const tm tm_now = sys_localtime(time_now); output << fmt(tm_now, "%FT%T."); // Fraction of a second part diff --git a/apps/support.maf b/apps/support.maf index e19588317..6ed1a6f5e 100644 --- a/apps/support.maf +++ b/apps/support.maf @@ -11,6 +11,7 @@ SOURCES apputil.cpp statswriter.cpp socketoptions.cpp +options.cpp transmitmedia.cpp uriparser.cpp verbose.cpp @@ -18,6 +19,7 @@ verbose.cpp PRIVATE HEADERS apputil.hpp socketoptions.hpp +options.hpp transmitbase.hpp transmitmedia.hpp uriparser.hpp diff --git a/apps/transmitbase.hpp b/apps/transmitbase.hpp index 4a28cd4cb..6caa03c8d 100644 --- a/apps/transmitbase.hpp +++ b/apps/transmitbase.hpp @@ -31,14 +31,14 @@ extern unsigned long transmit_chunk_size; struct MediaPacket { bytevector payload; - int64_t time = 0; + int64_t time_us = 0; MediaPacket(bytevector&& src) : payload(std::move(src)) {} - MediaPacket(bytevector&& src, int64_t stime) : payload(std::move(src)), time(stime) {} + MediaPacket(bytevector&& src, int64_t stime) : payload(std::move(src)), time_us(stime) {} - MediaPacket(size_t payload_size) : payload(payload_size), time(0) {} + MediaPacket(size_t payload_size) : payload(payload_size) {} MediaPacket(const bytevector& src) : payload(src) {} - MediaPacket(const bytevector& src, int64_t stime) : payload(src), time(stime) {} + MediaPacket(const bytevector& src, int64_t stime) : payload(src), time_us(stime) {} MediaPacket() {} }; diff --git a/apps/transmitmedia.cpp b/apps/transmitmedia.cpp index 9f7245be8..7f34a7b3c 100644 --- a/apps/transmitmedia.cpp +++ b/apps/transmitmedia.cpp @@ -68,7 +68,7 @@ class FileSource: public Source if (pkt.payload.size() < chunk) pkt.payload.resize(chunk); - pkt.time = 0; + pkt.time_us = 0; ifile.read(pkt.payload.data(), chunk); size_t nread = ifile.gcount(); if (nread < pkt.payload.size()) @@ -235,7 +235,7 @@ void SrtCommon::InitParameters(string host, map par) && transmit_chunk_size > SRT_LIVE_DEF_PLSIZE) { if (transmit_chunk_size > max_payload_size) - Error(fmtcat("Chunk size in live mode exceeds ", max_payload_size, " bytes; this is not supported")); + Error(ofcat("Chunk size in live mode exceeds ", max_payload_size, " bytes; this is not supported")); par["payloadsize"] = fmts(transmit_chunk_size); } @@ -320,7 +320,7 @@ bool SrtCommon::AcceptNewClient() { srt_close(m_bindsock); srt_close(m_sock); - Error(fmtcat("accepted connection's payload size ", maxsize, " is too small for required ", transmit_chunk_size, " chunk size")); + Error(ofcat("accepted connection's payload size ", maxsize, " is too small for required ", transmit_chunk_size, " chunk size")); } // we do one client connection at a time, @@ -500,7 +500,7 @@ void SrtCommon::ConnectClient(string host, int port) if (m_transtype == SRTT_LIVE && transmit_chunk_size > size_t(maxsize)) { srt_close(m_sock); - Error(fmtcat("accepted connection's payload size ", maxsize, " is too small for required ", transmit_chunk_size, " chunk size")); + Error(ofcat("accepted connection's payload size ", maxsize, " is too small for required ", transmit_chunk_size, " chunk size")); } SRTSTATUS stat = ConfigurePost(m_sock); @@ -591,7 +591,7 @@ SrtCommon::~SrtCommon() SrtSource::SrtSource(string host, int port, const map& par) { Init(host, port, par, false); - hostport_copy = fmtcat(host, ":"_V, port); + hostport_copy = ofcat(host, ":"_SV, port); } int SrtSource::Read(size_t chunk, MediaPacket& pkt, ostream &out_stats) @@ -609,7 +609,7 @@ int SrtSource::Read(size_t chunk, MediaPacket& pkt, ostream &out_stats) return stat; } - pkt.time = ctrl.srctime; + pkt.time_us = ctrl.srctime; chunk = size_t(stat); if (chunk < pkt.payload.size()) @@ -803,7 +803,7 @@ class ConsoleSource: public Source } // Save this time to potentially use it for SRT target. - pkt.time = srt_time_now(); + pkt.time_us = srt_time_now(); if (chunk < pkt.payload.size()) pkt.payload.resize(ret); @@ -1017,7 +1017,7 @@ class UdpCommon void Error(int err, string src) { - string message = SysStrError(err); + string message = sys_strerror(err); cerr << "\nERROR #" << err << ": " << message << endl; @@ -1100,7 +1100,7 @@ class UdpSource: public Source, public UdpCommon sa.len = si; // Save this time to potentially use it for SRT target. - pkt.time = srt_time_now(); + pkt.time_us = srt_time_now(); chunk = size_t(stat); if (chunk < pkt.payload.size()) pkt.payload.resize(chunk); diff --git a/apps/uriparser.cpp b/apps/uriparser.cpp index c143cb9af..41933d43f 100644 --- a/apps/uriparser.cpp +++ b/apps/uriparser.cpp @@ -65,7 +65,7 @@ string UriParser::makeUri() prefix = m_proto + "://"; } - ofmtbufstream out; + ofmt_bufs out; out.print(prefix, m_host); if ((m_port == "" || m_port == "0") && m_expect == EXPECT_FILE) @@ -74,28 +74,28 @@ string UriParser::makeUri() } else { - out.print(":"_V, m_port); + out.print(":"_SV, m_port); } if (m_path != "") { if (m_path[0] != '/') - out.print("/"_V); + out.print("/"_SV); out.print(m_path); } if (!m_mapQuery.empty()) { - out.print("?"_V); + out.print("?"_SV); query_it i = m_mapQuery.begin(); for (;;) { - out.print(i->first, "="_V, i->second); + out.print(i->first, "="_SV, i->second); ++i; if (i == m_mapQuery.end()) break; - out.print("&"_V); + out.print("&"_SV); } } diff --git a/logging/README.md b/logging/README.md index ac9d8402e..9e11d2415 100644 --- a/logging/README.md +++ b/logging/README.md @@ -8,11 +8,11 @@ Compat utilities ================ Header: `hvu_compat.h` -Source: `hvu_compat.c` +Source: `hvu_compat.cpp` Implements the following functions: -1. `SysStrError`: replicates the functionality of the `strerror` function +1. `sys_strerror`: replicates the functionality of the `strerror` function portable way. This function returns a message assigned to the given system error code. @@ -21,13 +21,10 @@ functions are not portable, there are 2 different versions on POSIX systems and there's a whole new procedure on Windows. This function should cover this functionality on all supported systems. -The version for C language requires an output buffer. An extra C++ version +The version with 3 arguments requires an output buffer. A convenience wrapper returns it as a string. -The version with C interface is named `hvu_SysStrError`, everything else is -in the `hvu` namespace. - -2. `SysLocalTime`: returns the `tm` structure for the local time basing on +2. `sys_localtime`: returns the `tm` structure for the local time basing on given value that should be the number of seconds since epoch. This should replicate the reentrant versions of the `localtime` function. @@ -221,6 +218,16 @@ LOGC(falog.Error, log << "Wrong value of " << x); The `LOGC` macro is the only possibility for C++03/C++98. The LOGP is still available if compiling in this mode, but it accepts only one message argument. +If you need to use the printf-style format arguments, you can use the `form` +method applied to the `log` variable: + +``` +LOGC(falog.Error, log.form("Wrong value of %d", x)); +``` + +The `vform` function is also available to expose the `vprintf`-like API. +Note that this uses an intermediate internal buffer limited to 512 characters. + For convenience these enabler macros enable also the use of the following convenience macros: diff --git a/logging/config-model.tcl b/logging/config-model.tcl index 64856c7b5..57cc1a941 100644 --- a/logging/config-model.tcl +++ b/logging/config-model.tcl @@ -66,8 +66,8 @@ set loggers_generallink gglog # For example, for 'external', the prefix will be "LF.ex" set loggers_prefix "LF." -# The namespace where the global logger variables and the logger config will -# be defined. Use dot separation rather than ::. +# The C++ namespace for the generated code where global logger variables and +# logger confg will be defined. Use dot separation rather than ::. # For example, this one below will be my::ns namespace. set loggers_namespace my.ns diff --git a/logging/hvu_compat.c b/logging/hvu_compat.cpp similarity index 82% rename from logging/hvu_compat.c rename to logging/hvu_compat.cpp index ba72ed00e..16071c846 100644 --- a/logging/hvu_compat.c +++ b/logging/hvu_compat.cpp @@ -8,19 +8,22 @@ * */ -// Implementation file for srt_compat.h /***************************************************************************** written by Haivision Systems Inc. *****************************************************************************/ -// Prevents from misconfiguration through preprocessor. +// This file contains the implementation for sys_strerror call +// with maximum portability. #include "hvu_compat.h" -#include -#include -#include +#include +#include +#include + +#include "ofmt.h" + #if defined(__unix__) && !defined(BSD) && !defined(SUNOS) #include #endif @@ -30,15 +33,15 @@ written by #include #endif +namespace hvu +{ -static const char* SysStrError_Fallback(int errnum, char* buf, size_t buflen) +static const char* sys_strerror_fallback(int errnum, char* buf, size_t buflen) { -#if defined(_MSC_VER) && _MSC_VER < 1900 - _snprintf(buf, buflen - 1, "ERROR CODE %d", errnum); - buf[buflen - 1] = '\0'; -#else - snprintf(buf, buflen, "ERROR CODE %d", errnum); -#endif + ofmt_bufs out; + out.print("ERROR CODE ", errnum); + size_t outsize = out.copy(buf, buflen - 1); + buf[outsize] = '\0'; return buf; } @@ -50,7 +53,7 @@ static const char* SysStrError_Fallback(int errnum, char* buf, size_t buflen) // a fallback message will be returned, either as returned by the underlying // function, or crafted by this function as a response to error in an // underlying function. -extern const char * hvu_SysStrError(int errnum, char * buf, size_t buflen) +extern const char * sys_strerror(int errnum, char * buf, size_t buflen) { if (buf == NULL || buflen < 4) // Required to put ??? into it as a fallback { @@ -61,7 +64,7 @@ extern const char * hvu_SysStrError(int errnum, char * buf, size_t buflen) buf[0] = '\0'; #if defined(_WIN32) - const char* lpMsgBuf; + const char* lpMsgBuf = NULL; // Note: Intentionally the "fixed char size" types are used despite using // character size dependent FormatMessage (instead of FormatMessageA) so that @@ -83,20 +86,18 @@ extern const char * hvu_SysStrError(int errnum, char * buf, size_t buflen) (LPSTR)&lpMsgBuf, 0, NULL); - if (lpMsgBuf) + if (!lpMsgBuf) { + return sys_strerror_fallback(errnum, buf, buflen); + } + #ifdef _MSC_VER - strncpy_s(buf, buflen, lpMsgBuf, _TRUNCATE); + strncpy_s(buf, buflen, lpMsgBuf, _TRUNCATE); #else - strncpy(buf, lpMsgBuf, buflen-1); - buf[buflen-1] = 0; + strncpy(buf, lpMsgBuf, buflen-1); + buf[buflen-1] = 0; #endif - LocalFree((HLOCAL)lpMsgBuf); - } - else - { - SysStrError_Fallback(errnum, buf, buflen); - } + LocalFree((HLOCAL)lpMsgBuf); return buf; @@ -111,7 +112,7 @@ extern const char * hvu_SysStrError(int errnum, char * buf, size_t buflen) // craft a fallback message in this case. if (strerror_r(errnum, buf, buflen) != 0) { - return SysStrError_Fallback(errnum, buf, buflen); + return sys_strerror_fallback(errnum, buf, buflen); } return buf; #else @@ -128,7 +129,7 @@ extern const char * hvu_SysStrError(int errnum, char * buf, size_t buflen) if (!gnu_buffer) { // This should never happen, so just a paranoid check - return SysStrError_Fallback(errnum, buf, buflen); + return sys_strerror_fallback(errnum, buf, buflen); } // If they are the same, the message is already copied @@ -142,3 +143,5 @@ extern const char * hvu_SysStrError(int errnum, char * buf, size_t buflen) return buf; #endif } + +} // namespace diff --git a/logging/hvu_compat.h b/logging/hvu_compat.h index 31cdc2cb9..2d1c3b3f1 100644 --- a/logging/hvu_compat.h +++ b/logging/hvu_compat.h @@ -17,22 +17,6 @@ written by #ifndef INC_HVU_COMPAT_H #define INC_HVU_COMPAT_H -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* Ensures that we store the error in the buffer and return the buffer. */ -const char * hvu_SysStrError(int errnum, char * buf, size_t buflen); - -#ifdef __cplusplus -} // extern C - - -// Extra C++ stuff. Included only in C++ mode. - - #include #include #include @@ -40,13 +24,16 @@ const char * hvu_SysStrError(int errnum, char * buf, size_t buflen); namespace hvu { -inline std::string SysStrError(int errnum) +// Ensures that we store the error in the buffer and return the buffer. +const char* sys_strerror(int errnum, char* buf, size_t buflen); + +inline std::string sys_strerror(int errnum) { char buf[1024]; - return ::hvu_SysStrError(errnum, buf, 1024); + return sys_strerror(errnum, buf, 1024); } -inline struct tm SysLocalTime(time_t tt) +inline struct tm sys_localtime(time_t tt) { using namespace std; @@ -68,6 +55,5 @@ inline struct tm SysLocalTime(time_t tt) } -#endif // defined C++ #endif // macroguard diff --git a/logging/logging.cpp b/logging/logging.cpp index 884759a1f..424bf43d2 100644 --- a/logging/logging.cpp +++ b/logging/logging.cpp @@ -308,22 +308,36 @@ LogDispatcher::Proxy::Proxy(LogDispatcher& guy, const char* f, int l, const std: } } -LogDispatcher::Proxy& LogDispatcher::Proxy::vform(const char* fmts, va_list ap) +LogDispatcher::Proxy& LogDispatcher::Proxy::form(const char* fmts, ...) { - static const int BUFLEN = 512; - char buf[BUFLEN]; + va_list ap; + va_start(ap, fmts); + vform(fmts, ap); + va_end(ap); + return *this; +} +static int Sys_vsnprintf(char* buf, const size_t BUFLEN, const char* fmts, va_list ap) +{ #if defined(_MSC_VER) && _MSC_VER < 1900 int wlen = _vsnprintf(buf, BUFLEN - 1, fmts, ap); + buf[BUFLEN - 1] = '\0'; //JIC #else int wlen = vsnprintf(buf, BUFLEN, fmts, ap); #endif + return wlen; +} + +LogDispatcher::Proxy& LogDispatcher::Proxy::vform(const char* fmts, va_list ap) +{ + static const int BUFLEN = 512; + char buf[BUFLEN]; + int wlen = Sys_vsnprintf(buf, BUFLEN, fmts, ap); if (wlen < 1) // catch both 0 and -1 { // ERROR when formatting - const char msg[] = ""; - os.write(msg, sizeof (msg)); + os.print(OFMT_SV("")); return *this; } @@ -339,11 +353,11 @@ LogDispatcher::Proxy& LogDispatcher::Proxy::vform(const char* fmts, va_list ap) --len; } - os.write(buf, len); + os.print(fmt_rawstr(buf, len)); return *this; } -void LogDispatcher::CreateLogLinePrefix(hvu::ofmtbufstream& serr) +void LogDispatcher::CreateLogLinePrefix(hvu::ofmt_bufs& serr) { using namespace std; using namespace hvu; @@ -356,7 +370,7 @@ void LogDispatcher::CreateLogLinePrefix(hvu::ofmtbufstream& serr) // Not necessary if sending through the queue. timeval tv; gettimeofday(&tv, NULL); - struct tm tm = hvu::SysLocalTime((time_t) tv.tv_sec); + struct tm tm = hvu::sys_localtime((time_t) tv.tv_sec); if (strftime(tmp_buf, sizeof(tmp_buf), "%X.", &tm)) { @@ -367,15 +381,15 @@ void LogDispatcher::CreateLogLinePrefix(hvu::ofmtbufstream& serr) // Note: ThreadName::get needs a buffer of size min. ThreadName::BUFSIZE if (!isset(HVU_LOGF_DISABLE_THREADNAME) && ThreadName::get(tmp_buf)) { - serr << OFMT_RAWSTR("/") << tmp_buf; + serr << OFMT_SV("/") << tmp_buf; } if (!isset(HVU_LOGF_DISABLE_SEVERITY)) { - serr.write(prefix, prefix_len); // include terminal 0 + serr << fmt_rawstr(prefix, prefix_len); // include terminal 0 } - serr << OFMT_RAWSTR(": "); + serr << OFMT_SV(": "); } #undef HVU_LOG_STATIC_ASSERT diff --git a/logging/logging.h b/logging/logging.h index d320e8517..6fc83f1a3 100644 --- a/logging/logging.h +++ b/logging/logging.h @@ -173,7 +173,7 @@ class LogDispatcher bool IsEnabled() { return enabled; } - void CreateLogLinePrefix(hvu::ofmtbufstream&); + void CreateLogLinePrefix(hvu::ofmt_bufs&); void SendLogLine(const char* file, int line, const std::string& area, const std::string& sl); // log.Debug("This is the ", nth, " time"); <--- C++11 only. @@ -217,7 +217,7 @@ class LogDispatcher { LogDispatcher& that; - hvu::ofmtbufstream os; + hvu::ofmt_bufs os; // CACHE!!! const char* i_file; @@ -299,14 +299,16 @@ class LogDispatcher if (that.IsEnabled()) { if ((flags & HVU_LOGF_DISABLE_EOL) == 0) - os << OFMT_RAWSTR("\n"); // XXX would be nice to use a symbol for it + os << OFMT_SV("\n"); // XXX would be nice to use a symbol for it that.SendLogLine(i_file, i_line, area, os.str()); } // XXX Consider clearing the 'os' manually } + // Convenience only. Accessible through LOGC. Proxy& vform(const char* fmts, va_list ap); + Proxy& form(const char* fmts, ...); }; diff --git a/logging/ofmt.h b/logging/ofmt.h index 23a95ce9a..b95a8f4bd 100644 --- a/logging/ofmt.h +++ b/logging/ofmt.h @@ -17,32 +17,9 @@ written by // // This is a header-only lightweight C++03-compatible formatting library, // which provides the on-demand tagged format API and iostream-style wrapper -// for FILE type from stdio. It has nothing to do with the rest of the {fmt} -// library, except that it reuses the namespace. +// for ostream-based types. -// USAGE: -// -// 1. Using iostream style: -// -// ofmtbufstream sout; -// -// sout << "Value: " << v << " (" << fmt(v, fmtc().hex().width(2).fillzero()) << ")\n"; -// -// NOTE: When passing a string literal, consider using "Value"_V (C++11 only) -// or OFMT_RAWSTR("Value"). Unfortunately C++ doesn't distinguish "Value" and -// char [20] v = "Value"; both here contain "Value\0", but sizeof(v) for them -// returns the size of the allocated space, not size of the string. Although -// the compiler should expand strlen() in place for literals, note that it -// won't do it if optimizations are turned off. -// -// 2. Using variadic style: -// -// sout.print("Value: ", v, " (", fmt(v, fmtc().hex().width(2).fillzero()), ")\n"); -// -// -// OFMT has also a potential to be used together with iostream, but it requires more -// definition support. This is only the basic fragment to be used with the logging system, -// hence it provides only a wrapper over std::stringstream. +// Follow the ofmt.md file for details. #ifndef INC_HVU_OFMT_H #define INC_HVU_OFMT_H @@ -60,10 +37,22 @@ written by #define OFMT_HAVE_CXX11 0 #endif +// It's safest to bet 2000 + STDNUM + 00, as there is no +// month number 0 (201701 is January 2017). +#if (defined(__cplusplus) && __cplusplus > 201700L) +#define OFMT_HAVE_CXX17 1 +#else +#define OFMT_HAVE_CXX17 0 +#endif + #if OFMT_HAVE_CXX11 #include #endif +#if OFMT_HAVE_CXX17 +#include +#endif + namespace hvu { @@ -210,30 +199,135 @@ typedef basic_fmtc wfmtc; namespace internal { +#if OFMT_HAVE_CXX17 +typedef std::string_view ofmt_stringview; +#else +// !!! IMPORTANT !!! +// THIS CLASS IS FOR THE PURPOSE OF DIRECT WRITING TO THE STREAM ONLY. +// DO NOT use this class for any other purpose and use it also with +// EXTREME CARE. +// The only role of this class is to pass the string with KNOWN SIZE +// written in either a string literal or an array of characters to +// the output stream using its `write` method, that is, with bypassing +// any formatting facilities. +struct ofmt_stringview +{ +private: + const char* d; + size_t s; + +public: + explicit ofmt_stringview(const char* dd, size_t ss): d(dd), s(ss) {} + + const char* data() const { return d; } + size_t size() const { return s; } + + const char* begin() const { return d; } + const char* end() const { return d + s; } +}; +#endif + +template +struct check_minus_1 +{ + static const size_t value = N - 1; +}; + +template<> +struct check_minus_1<0> +{ +}; + +// NOTE: DO NOT USE THIS FUNCTION DIRECTLY. +template +inline ofmt_stringview CreateRawString_FWD(const char (&ref)[N]) +{ + const char* ptr = ref; + return ofmt_stringview(ptr, check_minus_1::value); +} + + // Use this as an overload for operator<< for a stream // in order to make it support every possible sender produced by fmt(). template -struct fmt_proxy_template +struct fmt_proxy_template: public SenderType { + typedef SenderType sender_t; + const Value& val; // ERROR: invalidly declared function? --> // Iostream manipulators should not be sent to the stream. - // use fmt() with fmtc() instead. - SenderType snd; + // use fmt() instead. - fmt_proxy_template(const Value& v, const SenderType& s): val(v), snd(s) {} + fmt_proxy_template(const Value& v, const SenderType& s): SenderType(s), val(v) {} template void sendto(OutStream& os) const { - snd.format_send(val, os); + sender_t::format_send(val, os); } }; // Simple sender: fmt(value) -struct snd_simple +struct snd_default +{ + snd_default() {} + + // General version for all values + template + void format_send(const Value& val, OutStream& os) const + { + std::stringstream tmp; + tmp << val; + if (tmp.tellp() > 0) + { + tmp.clear(); // clear the EOF flag that prevents copying + os << tmp.rdbuf(); + } + } + + // Specializations for direct types + // XXX Add also a version for std::string_view, if C++17. + template + void format_send(const internal::ofmt_stringview& val, OutStream& os) const + { + os.write(val.data(), val.size()); + } + + template + void format_send(const char* val, OutStream& os) const + { + size_t len = strlen(val); + format_send(internal::ofmt_stringview(val, len), os); + } + + /* + // XXX This cannot be enabled because it makes the lvalue of type + // const char[N] converted equally to const char (&)[N] and const char*; + // OTOH it must be resolved to const char* eventually anyway because N is + // the size of the spare buffer and not necessarily the size of the string. + // To pass the static string's size directly, use OFMT_SV macro or the + // _SV operator (note: sv operator in C++17 should also work). + + template + void format_send(const char (&t)[N], OutStream& os) const + { + const char* raw = t; + format_send(t, os); + } + // */ + + template + void format_send(const std::string& val, OutStream& os) const + { + os.write(val.data(), val.size()); + } +}; + +struct snd_default_stateous { - snd_simple() {} + snd_default_stateous() {} + // General version for all values template void format_send(const Value& val, OutStream& os) const { @@ -256,23 +350,34 @@ struct snd_fmtc std::stringstream tmp; format_spec.apply(tmp); tmp << val; - os << tmp.rdbuf(); + if (tmp.tellp() > 0) + { + tmp.clear(); // clear the EOF flag that prevents copying + os << tmp.rdbuf(); + } } }; // same as snd_fmtc, but without isolating the stream -// for the call to fmrx(value, fmtc()...) +// for the call to fmtx(value, fmtc()...) template -struct snd_stateous +struct snd_fmtc_stateous { basic_fmtc format_spec; - snd_stateous(const basic_fmtc& f): format_spec(f) {} + snd_fmtc_stateous(const basic_fmtc& f): format_spec(f) {} template void format_send(const Value& val, OutStream& os) const { - format_spec.apply_ontop(os); - os << val; + std::stringstream tmp; + tmp.copyfmt(os); + format_spec.apply_ontop(tmp); + tmp << val; + if (tmp.tellp() > 0) + { + tmp.clear(); // clear the EOF flag that prevents copying + os << tmp.rdbuf(); + } } }; @@ -288,31 +393,64 @@ inline void snd_ios_manipulate(Stream& os, const Manip& man) template inline void snd_ios_manipulate(Stream& os, const std::pair& mans) { - os << mans.first << mans.second; + os << (mans.first) << (mans.second); } +// Handle special type manipulators like `endl` +// Unfortunately it must be handled directly in every API + +// The idea behind the below "sequence" type is to provide something like +// std::tuple, however this type needs to distinguish two types of manipulators: +// the ones with their own distinct type, and those being functions (including +// function templates, as it's with "endl"). The use of std::tuple requires too +// many too complicated declaration to work this around. + +typedef std::ostream& ostream_manip_fn(std::ostream&); + +template +using anystream_manip_fn = StreamType& (StreamType&); + #if OFMT_HAVE_CXX11 -template -struct snd_ios_man_tuple +template +struct sequence; + +template<> +struct sequence<> { - static void send(Stream& s, const Tuple& t) - { - snd_ios_man_tuple::send(s, t); - snd_ios_manipulate(s, std::get(t)); - } + sequence() {} +}; + +template +struct sequence, Types...>: sequence +{ + typedef sequence base_t; + anystream_manip_fn* head; + const sequence& next() const { return *this; } + sequence& next() { return *this; } + sequence(const anystream_manip_fn& arg1, const Types&... args): head(&arg1), base_t(args...) {} }; -template -struct snd_ios_man_tuple<0, Tuple, Stream> +template +struct sequence: sequence { - static void send(Stream&, const Tuple&) {} + typedef sequence base_t; + Type1 head; + const sequence& next() const { return *this; } + sequence& next() { return *this; } + sequence(const Type1& arg1, const Types&... args): head(arg1), base_t(args...) {} }; -template -inline void snd_ios_manipulate(Stream& os, const std::tuple& mans) + +template +inline void snd_ios_manipulate(Stream& os, const sequence& mans) +{ + os << mans.head; + snd_ios_manipulate(os, mans.next()); +} + +template +inline void snd_ios_manipulate(Stream& os, const sequence<>& mans) { - typedef std::tuple Tuple; - snd_ios_man_tuple::value, Tuple, Stream>::send(os, mans); } #endif @@ -320,7 +458,7 @@ template struct snd_ios { private: - const Manip& manip; + Manip manip; // Can't use reference - could happen to be to temporary. public: snd_ios(const Manip& m): manip(m) {} @@ -334,229 +472,151 @@ struct snd_ios } }; -// !!! IMPORTANT !!! -// THIS CLASS IS FOR THE PURPOSE OF DIRECT WRITING TO THE STREAM ONLY. -// DO NOT use this class for any other purpose and use it also with -// EXTREME CARE. -// The only role of this class is to pass the string with KNOWN SIZE -// written in either a string literal or an array of characters to -// the output stream using its `write` method, that is, with bypassing -// any formatting facilities. -struct fmt_stringview -{ -private: - const char* d; - size_t s; - -public: - explicit fmt_stringview(const char* dd, size_t ss): d(dd), s(ss) {} - - const char* data() const { return d; } - size_t size() const { return s; } - - const char* begin() const { return d; } - const char* end() const { return d + s; } -}; +} // END: namespace internal -template -struct check_minus_1 +template +inline internal::fmt_proxy_template fmt_make_proxy(const Value& v, const SenderType& s) { - static const size_t value = N - 1; -}; + return internal::fmt_proxy_template(v, s); +} -template<> -struct check_minus_1<0> +inline internal::ofmt_stringview fmt_rawstr(const char* dd, size_t ss) { -}; + return internal::ofmt_stringview(dd, ss); +} -// NOTE: DO NOT USE THIS FUNCTION DIRECTLY. -template -inline fmt_stringview CreateRawString_FWD(const char (&ref)[N]) +inline internal::ofmt_stringview fmt_rawstr(const std::string& s) { - const char* ptr = ref; - return fmt_stringview(ptr, check_minus_1::value); + return internal::ofmt_stringview(s.data(), s.size()); } -} // END: namespace internal - -inline internal::fmt_stringview fmt_rawstr(const char* dd, size_t ss) +template inline +internal::fmt_proxy_template fmt(const Value& val) { - return internal::fmt_stringview(dd, ss); + return fmt_make_proxy(val, internal::snd_default()); } -inline internal::fmt_stringview fmt_rawstr(const std::string& s) +template inline +internal::fmt_proxy_template > fmt(const Value& val, const basic_fmtc& config) { - return internal::fmt_stringview(s.data(), s.size()); + return fmt_make_proxy(val, internal::snd_fmtc(config)); } template inline -internal::fmt_proxy_template fmt(const Value& val) +internal::fmt_proxy_template fmtx(const Value& val) { - using namespace internal; - return fmt_proxy_template(val, snd_simple()); + return fmt_make_proxy(val, internal::snd_default_stateous()); } template inline -internal::fmt_proxy_template > fmt(const Value& val, const basic_fmtc& config) +internal::fmt_proxy_template > fmtx(const Value& val, const basic_fmtc& config) { - using namespace internal; - return fmt_proxy_template >(val, snd_fmtc(config)); + return fmt_make_proxy(val, internal::snd_fmtc_stateous(config)); } -template inline -internal::fmt_proxy_template > fmtx(const Value& val, const basic_fmtc& config) +template inline +internal::fmt_proxy_template > fmtm(const Value& val, const Manip& man) { - using namespace internal; - return fmt_proxy_template >(val, snd_stateous(config)); + return fmt_make_proxy(val, internal::snd_ios(man)); } -template inline -internal::fmt_proxy_template > fmt(const Value& val, const Manip& man) +template inline +internal::fmt_proxy_template*> > + fmtm(const Value& val, const internal::anystream_manip_fn& man) { - using namespace internal; - return fmt_proxy_template >(val, snd_ios(man)); + return fmt_make_proxy(val, internal::snd_ios*>(man)); } #if OFMT_HAVE_CXX11 template inline -internal::fmt_proxy_template> > - fmt(const Value& val, const Manip1& man1, const Manip&... mans) +internal::fmt_proxy_template> > + fmtm(const Value& val, const Manip1& man1, const Manip&... mans) { - typedef std::tuple Tuple; - using namespace internal; - - return fmt_proxy_template >(val, snd_ios(Tuple(man1, mans...))); + typedef internal::sequence Tuple; + return fmt_make_proxy(val, internal::snd_ios(Tuple(man1, mans...))); } #else template inline -internal::fmt_proxy_template > > fmt(const Value& val, const Manip1& man, const Manip2& man2) +internal::fmt_proxy_template > > fmtm(const Value& val, const Manip1& man1, const Manip2& man2) { typedef std::pair Tuple; - using namespace internal; - return fmt_proxy_template >(val, snd_ios(Tuple(man, man2))); + return fmt_make_proxy(val, internal::snd_ios(Tuple(man1, man2))); } #endif -inline const char* fmt_if(bool value, const char* strue, const char* sfalse) +// Simple transformer +inline const char* fmt_if(bool value, const char* strue, const char* sfalse = "") { return value ? strue : sfalse; } -// XXX Make basic_ofmtbufstream etc. -class ofmtbufstream +namespace internal { - friend class ofmtrefstream; -protected: - std::stringstream buffer; - -public: - ofmtbufstream() {} - - std::ostream& base() { return buffer; } - - // Extra constructor that allows the stream to have some - // initial contents. Only string types supported - ofmtbufstream(const internal::fmt_stringview& s) - { - buffer.write(s.data(), s.size()); - } +struct snd_boolean +{ + std::string strue, sfalse; + snd_boolean(const std::string& st, + const std::string& sf = std::string()): + strue(st), sfalse(sf) + {} - ofmtbufstream(const std::string& s) + template + void format_send(const Value& val, OutStream& os) const { - buffer.write(s.data(), s.size()); + os << (val ? strue : sfalse); } +}; +} - // This is to allow pre-configuration before sending. - // Only use fmtx (not fmt) if you want to keep the state. - void setup(const basic_fmtc& fc) - { - fc.apply(buffer); - } +inline internal::fmt_proxy_template fmt_if( + bool val, + const std::string& st, + const std::string& sf = std::string()) +{ + using namespace internal; + return fmt_proxy_template(val, snd_boolean(st, sf)); +} - void clear() - { - buffer.clear(); - } +namespace internal +{ +struct ofmtbase_ref +{ +protected: + std::ostream& refstream; +public: - // Expose - ofmtbufstream& write(const char* buf, size_t size) - { - buffer.write(buf, size); - return *this; - } + ofmtbase_ref(std::ostream& src): refstream(src) {} - ofmtbufstream& operator<<(const char* t) - { - size_t len = std::strlen(t); - buffer.write(t, len); - return *this; - } + std::ostream& base() {return refstream;} +}; - // Treat a fixed-size array just like a pointer - // to the first only and still use strlen(). This - // is because it usually designates a buffer that - // has N as the spare space, so you still need to - // mind the NUL terminator character. For string - // literals you should use OFMT_RAWSTR macro that - // gets the set of pointer and size from the string - // as an array, but also makes sure that the argument - // is a string literal. - // Unfortunately C++ is unable to distinguish the - // fixed array (with spare buffer space) from a string - // literal (which has only one extra termination character). - // The compiler still can usually call strlen at - // compile time, but not if you are in a debug mode. - template - ofmtbufstream& operator<<(const char (&t)[N]) - { - size_t len = std::strlen(t); - buffer.write(t, len); - return *this; - } +struct ofmtbase_buf +{ +protected: + std::stringstream buffer; +public: - ofmtbufstream& operator<<(const std::string& s) - { - buffer.write(s.data(), s.size()); - return *this; - } + std::ostream& base() { return buffer; } - // XXX Add also a version for std::string_view, if C++17. - ofmtbufstream& operator<<(const internal::fmt_stringview& s) + ofmtbase_buf() {} + ofmtbase_buf(const std::string& s) { buffer.write(s.data(), s.size()); - return *this; } - template - ofmtbufstream& operator<<(const internal::fmt_proxy_template& prox) + size_t copy(char* buf, size_t bufsize) { - prox.sendto(buffer); - return *this; + return buffer.readsome(buf, bufsize); } - template inline - ofmtbufstream& operator<<(const Value& val) + // Provided so that you can prepare a big enough buffer to copy + size_t size() const { - return *this << fmt(val); - } - - // A utility function to send the argument directly - // to the buffer - template inline - ofmtbufstream& forward(const Value& val) - { - buffer << val; - return *this; - } - - ofmtbufstream& operator<<(const ofmtbufstream& source) - { - buffer << source.buffer.rdbuf(); - return *this; + return buffer.rdbuf()->in_avail(); } std::string str() const @@ -564,126 +624,110 @@ class ofmtbufstream return buffer.str(); } -// Additionally for C++11 -#if OFMT_HAVE_CXX11 - void print_chain() - { - } - - template - void print_chain(const Arg1& arg1, const Args&... args) + std::stringbuf* rdbuf() const { - *this << arg1; - print_chain(args...); + return buffer.rdbuf(); } - template - ofmtbufstream& print(const Args&... args) + void clear() { - print_chain(args...); - return *this; + buffer.clear(); } - template - ofmtbufstream& puts(const Args&... args) - { - print_chain(args...); - buffer << std::endl; - return *this; - } -#endif }; -class ofmtrefstream -{ -protected: - std::ostream& refstream; +} +template +class tp_ofmtstream: public Imp +{ public: - ofmtrefstream(std::ostream& src) : refstream(src) {} - std::ostream& base() { return refstream; } +#if OFMT_HAVE_CXX11 + // For C++11 try to just use inheriting constructors + // so that you play safe. This gives this class also + // more flexibility. + using Imp::Imp; +#else - // Expose - ofmtrefstream& write(const char* buf, size_t size) - { - refstream.write(buf, size); - return *this; - } + // For C++03 simply replicate both constrcutors, even + // if only some of them make sense. In this version also + // this class cannot be used with a custom value type. + tp_ofmtstream(std::ostream& src) : IMp(src) {} - ofmtrefstream& operator<<(const char* t) + tp_ofmtstream() {} + + // Extra constructor that allows the stream to have some + // initial contents. Only string types supported + tp_ofmtstream(const internal::ofmt_stringview& s): Imp(s) {} + + tp_ofmtstream(const std::string& s): Imp(s) {} +#endif + + using Imp::base; + + template + tp_ofmtstream& operator<<(const internal::fmt_proxy_template& prox) { - size_t len = std::strlen(t); - this->write(t, len); + prox.sendto(Imp::base()); return *this; } - // Treat a fixed-size array just like a pointer - // to the first only and still use strlen(). This - // is because it usually designates a buffer that - // has N as the spare space, so you still need to - // mind the NUL terminator character. For string - // literals you should use OFMT_RAWSTR macro that - // gets the set of pointer and size from the string - // as an array, but also makes sure that the argument - // is a string literal. - // Unfortunately C++ is unable to distinguish the - // fixed array (with spare buffer space) from a string - // literal (which has only one extra termination character). - // The compiler still can usually call strlen at - // compile time, but not if you are in a debug mode. - template - ofmtrefstream& operator<<(const char (&t)[N]) - { - size_t len = std::strlen(t); - this->write(t, len); + template inline + tp_ofmtstream& operator<<(const Value& val) + { + DefaultSender().format_send(val, Imp::base()); return *this; } - ofmtrefstream& operator<<(const std::string& s) + template + tp_ofmtstream& operator<<(const tp_ofmtstream& source) { - this->write(s.data(), s.size()); + Imp::base() << source.rdbuf(); return *this; } - // XXX Add also a version for std::string_view, if C++17. - ofmtrefstream& operator<<(const internal::fmt_stringview& s) + tp_ofmtstream& operator<<(const std::stringstream& source) { - this->write(s.data(), s.size()); + Imp::base() << source.rdbuf(); return *this; } - template - ofmtrefstream& operator<<(const internal::fmt_proxy_template& prox) + tp_ofmtstream& operator<<(internal::ostream_manip_fn& man) { - prox.sendto(refstream); + Imp::base() << (&man); return *this; } - template inline - ofmtrefstream& operator<<(const Value& val) + tp_ofmtstream& operator<<(const fmtc& fc) { - return *this << fmt(val); + fc.apply(Imp::base()); + return *this; } // A utility function to send the argument directly // to the buffer - template inline - ofmtrefstream& forward(const Value& val) + tp_ofmtstream& fwd() { - refstream << val; return *this; } - ofmtrefstream& operator<<(const ofmtbufstream& source) + template + tp_ofmtstream& fwd(const Value& val, const Args&... args) { - refstream << source.buffer.rdbuf(); - return *this; + Imp::base() << val; + return fwd(args...); + } + + template + tp_ofmtstream& fwd(internal::ostream_manip_fn* man, const Args&... args) + { + Imp::base() << man; + return fwd(args...); } // Additionally for C++11 -#if (defined(__cplusplus) && __cplusplus > 199711L) \ - || (defined(_MSVC_LANG) && _MSVC_LANG > 199711L) // Some earlier versions get this wrong +#if OFMT_HAVE_CXX11 void print_chain() { } @@ -696,98 +740,152 @@ class ofmtrefstream } template - ofmtrefstream& print(const Args&... args) + tp_ofmtstream& print(const Args&... args) { print_chain(args...); return *this; } template - ofmtrefstream& puts(const Args&... args) + tp_ofmtstream& printl(const Args&... args) { print_chain(args...); - refstream << std::endl; + Imp::base() << std::endl; return *this; } #endif }; +typedef tp_ofmtstream ofmt_bufs; +typedef tp_ofmtstream ofmt_bufx; + +typedef tp_ofmtstream ofmt_refs; +typedef tp_ofmtstream ofmt_refx; + +template inline +std::string fmts(const Value& val) +{ + ofmt_bufs out; + out << val; + return out.str(); +} + +template inline +std::string fmts(const Value& val, const fmtc& fmtspec) +{ + ofmt_bufs out; + out << fmt(val, fmtspec); + return out.str(); +} + + // Additionally for C++11 -#if (defined(__cplusplus) && __cplusplus > 199711L) \ - || (defined(_MSVC_LANG) && _MSVC_LANG > 199711L) // Some earlier versions get this wrong +#if OFMT_HAVE_CXX11 -inline internal::fmt_stringview operator""_V(const char* ptr, size_t s) +template +inline Stream& ofwd(Stream& out) { - return internal::fmt_stringview(ptr, s); + return out; +} + +template +inline Stream& ofwd(Stream& out, const Arg1& arg1, const Args&... args) +{ + out << arg1; + return ofwd(out, args...); +} + +template +inline Stream& ofprint(Stream& out, const Arg1& arg1, const Args&... args) +{ + ofmt_refs sout(out); + sout.print(arg1, args...); + return out; +} + +template +inline Stream& ofprintl(Stream& out, const Args&... args) +{ + ofmt_refs sout(out); + sout.printl(args...); + return out; +} + +template +inline Stream& ofprintx(Stream& out, const Arg1& arg1, const Args&... args) +{ + ofmt_bufx sout; + sout.base().copyfmt(out); + sout.print(arg1, args...); + out << sout.rdbuf(); + return out; +} + +template +inline Stream& ofprintxl(Stream& out, const Arg1& arg1, const Args&... args) +{ + ofmt_bufx sout; + sout.base().copyfmt(out); + sout.printl(arg1, args...); + out << sout.rdbuf(); + return out; +} +inline internal::ofmt_stringview operator""_SV(const char* ptr, size_t s) +{ + return internal::ofmt_stringview(ptr, s); } template inline -std::string fmtcat(const Args&... args) +std::string ofcat(const Args&... args) { - ofmtbufstream out; + ofmt_bufs out; out.print(args...); return out.str(); } #else -// Provide fmtcat for C++03 for up to 4 parameters +// Provide ofcat for C++03 for up to 4 parameters // The 1-argument version is for logical consistency. template inline -std::string fmtcat(const Arg1& arg1) +std::string ofcat(const Arg1& arg1) { return fmts(arg1); } template inline -std::string fmtcat(const Arg1& arg1, const Arg2& arg2) +std::string ofcat(const Arg1& arg1, const Arg2& arg2) { - ofmtbufstream out; + ofmt_bufs out; out << arg1 << arg2; return out.str(); } template inline -std::string fmtcat(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3) +std::string ofcat(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3) { - ofmtbufstream out; + ofmt_bufs out; out << arg1 << arg2 << arg3; return out.str(); } template inline -std::string fmtcat(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3, const Arg4& arg4) +std::string ofcat(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3, const Arg4& arg4) { - ofmtbufstream out; + ofmt_bufs out; out << arg1 << arg2 << arg3 << arg4; return out.str(); } #endif -template inline -std::string fmts(const Value& val) -{ - ofmtbufstream out; - out << val; - return out.str(); -} - -template inline -std::string fmts(const Value& val, const fmtc& fmtspec) -{ - ofmtbufstream out; - out << fmt(val, fmtspec); - return out.str(); -} - } // This prevents the macro from being used with anything else -// than a string literal. Version of ""_V UDL available for C++03. -#define OFMT_RAWSTR(arg) ::hvu::internal::CreateRawString_FWD("" arg) +// than a string literal. Version of ""_SV UDL available for C++03. +#define OFMT_SV(arg) ::hvu::internal::CreateRawString_FWD("" arg) diff --git a/logging/ofmt.md b/logging/ofmt.md index 0d7123105..90b20fd0d 100644 --- a/logging/ofmt.md +++ b/logging/ofmt.md @@ -1,58 +1,130 @@ -Introduction -============ - -The iostream library has many advantages over the old C's printf function, -but the design has flaws: the formatting specification exists as a state of -the stream, which can be modified on the fly through manipulators. -For example, once you send `hex` manipulator into the stream, all integers will -be printed in hex since this time, until you change it back to `dec`. -This includes the instruction of sending to a stream happening anyhow next, -including after some instructions or in another function. The rules for -iostream manipulators only state that the `width` parameter is reset to 0, -and only after printing value of a type, for which the `operator<<` is -overloaded in the C++ standard library. All other parameters just get -changed and there's even no method to reset settings to system defaults -(you can do it by doing save-and-restore configuration, but it's on your -head as well to remember the system configuration in the beginning, which -is actually impossible in case of a library). +# Introduction + +OFMT is the output formatting helper tool cooperating with the standard +IOSTREAM library and providing the "on-demand tagged API". This allows to +provide configuration for formatting values in isolation, or as stateous +for a local scope, and additionally wrapper classes providing additional API. + +Example: + +``` +int x = 16, y = 10; +cout << fmt(x, fmtc().hex()) << " " << y << endl; +``` + +So it prints: +`` +10 10 +`` + +# Basic usage + +USAGE: + +1. Using iostream style: + +``` +ofmt_refs sout(cout); +sout << "Value: " << v << " (" << fmt(v, fmtc().hex().width(2).fillzero()) << ")\n"; +``` + +NOTE 1: You can use the `fmt` function also directly with ostream-based +objects, you just need to include `"ofmt_iostream.h"` and note that the stream +wrapper classes (ofmt_bufs and ofmt_refs) always pass string values +directly (bypassing the formatting facility). If you use ostream directly, you +can force this bypassing by using `fmt_rawstr()` for strings every time. + +NOTE 2: When passing a string literal, consider using `"Value"_SV` (C++11 only) +or `OFMT_SV("Value")`. Unfortunately C++ doesn't distinguish between `"Value"` +and `char v[20] = "Value"`; both here contain `"Value\0"`, but `sizeof()` for +them returns 6 and 20 respectively, as it's the reserved size, not string size. +Although the compiler should expand `strlen()` in place for literals, note it +might not do it with turned off optimizations. + +2. Using variadic style (C++11 only): + +``` +ofmt_refs sout(cout); +sout.print("Value: ", v, " (", fmt(v, fmtc().hex().width(2).fillzero()), ")\n"); +``` + +or + +``` +ofmt_refs sout(cout); +sout.printl("Value: ", v, " (", fmt(v, fmtc().hex().width(2).fillzero()), ")"); +``` + +or + +``` +ofprintl(cout, "Value: ", v, " (", fmt(v, fmtc().hex().width(2).fillzero()), ")"); +``` + +The following versions for the inline fmt* calls are available: + +* `fmt(value)` - uses the default formatting (for `ofmt*stream` classes, all +values other than string holders pass through it automatically) + +* `fmt(value, fmtc()...)` - uses configuration-specific isolated formatting + +* `fmtx(value)` - sends the value using the current formatting of the stream + +* `fmtx(value, fmtc()...)` - like above, but fmtc object modifies the stream's +state for the value, on top of the current settings + +* `fmtm(value, manip1, manip2...)` - isolated formatting with iostream manips + +* `fmt_if(value, s1, s2)` - passes s1 if `value == true` and s2 otherwise + + +# Motivation + +The iostream library has many advantages over the old C's printf function, but +the design has flaws: the formatting settings can be only changed as a stream +state (through methods or inline manipulators). For example, once you send +`hex` manipulators to the stream, all integer values are printed in hex until +you change it back to `dec` - including other far away instructions and other +functions. Only the `width` parameter is reset to 0 after printing a value of +type for which `operator<<` is defined in the standard library. To work this +around you'd have to save and restore stream state by yourself, there is even +no facility to do some "push and pop settings" for the stream. This approach +may be desired sometimes, but mostly it is a drawback, of which users are often +unaware, especially accustomed to printf, where default formatting settings +are always the same, or some settings are always specified explicitly. To fix this problem, you can use the so-called On-Demand Tagged API. That is, instead of specifying the configuration by changing the stream's state, you can choose to just apply a format specification for a single value using the -supplied `fmt` function: +supplied `fmt` function, while the stream state remains untouched: ``` cout << fmt(x, fmtc().hex()) << " " << y; // y printed as dec ``` -(Note here that to compile this fragment, you need to include `ofmt_iostream.h`). +The `fmtc` structure is provided for the following reasons: -The formatting specification is using the `fmtc` structure to achieve it, -which prevents from polluting the global namespace with too many names -as well as allows to save the configuration in a variable so that it can -be specified for multiple values: +* Allows for C++03 compatibility (infinite call chain) +* Does not pollute the namespace with freestanding manipulator names +* The settings philosophy is made closer to the one of printf tags +* Provides an ability to save the configuration in a single object: ``` fmtc hex04 = fmtc().hex().fillzero().width(4); cout << fmt(a, hex04) << ":" << fmt(b, hex04); ``` -Although for a single manipulator there is prepared also a simplified version -using the iomanip manipulators: +You can also use iostream manipulators directly. Note that the version for +C++03 supports only up to 2 manipulators. ``` -cout << fmt(x, hex) << " " << y; // y printed as dec +cout << fmtm(x, hex) << " " << y; // y printed as dec ``` -(Potentially this can be extended to handle multiple iomanip formatters, -but it's not implemented yet). - -This solution is compatible with C++98/C++03 version. Some alternative, more -comfortable API is provided for C++11. +This solution is compatible with C++98/C++03 version, but if you compile +in C++11 mode, some more advanced API is inaccessible. - -Formatting flags -================ +# Formatting flags The following configuration items are available in `fmtc` type: @@ -76,48 +148,52 @@ The following configuration items are available in `fmtc` type: `char` or `wchar_t` types, and also sets `internal` adjustment field.) Note that all of them are implemented as methods that return "itself", so -you can bind settings in chain: +you can bind settings in chain. You can also create local variable for this +type so that you can define the format specification and use in multiple `fmt` +calls: ``` - fmtc().hex().width(8).fillzero().showbase() +fmtc phex8 = fmtc().hex().width(8).fillzero().showbase(); ``` -You can also create local variable for this type so that you can define -the format specification and use in multiple `fmt` calls: +# Formatting stream utility -``` -fmtc phex8 = fmtc().hex().width(8).fillzero().showbase(); -``` +The general idea for the on-demand tagged API is that the stream state +remains the same and other settings only apply to the single value. But +the iostream's streams don't work this way. Therefore here are available +stream classes that should do: `ofmt_bufs` and `ofmt_refs`. They +provide the right overloads of the `operator<<`, which accept 3 types +of arguments: + +* The format-value proxy object, which should print the value with +explicit format settings (returned by the helper `fmt` method) -The `width`, `precision` and `fill` methods have a parameter and this way -they can use as well a runtime value. +* The string holder types (`std::string`, string view type and the raw +NUL-terminated string), which are printed directly through `write` method -Formatting stream utility -========================= +* Any other type of the value, which is passed through `fmt` with one +argument and then redirected to the first one -The idea for stream manipulation with on-demand tagged API is that there are -overloads for several types known as "string form", in which case this -string is directly written into the stream, with bypassing any formatting. -Everything else is passed through the `fmt` function with no config spec, -that is, it will use default formatting. The `fmt` function returns a proxy -object, which will employ std::stringstream for formatting, and then in order -to send to the output stream it will use buffer-to-buffer copy. +The intermediate formatting is done by the locally created `std::stringstream` +object, which is then copied to the stream's base object by buffer-to-buffer +copy. The stream's base object is `std::stringstream` object in `ofmt_bufs` +and the referred object of `std::ostream` type in `ofmt_refs`. -Hence the `ofmtbufstream` class is provided, which is a wrapper around -`std::stringstream` and should be used instead of it in order to utilize the -on-demand tagged API. Beside the "traditional" overloads for `operator<<`, it -provides also the "print" function, which uses multiple arguments ("puts" -additionally adds the end-of-line, just like the standard C "puts" function -does). This function is only available in C++11 version. +Beside the "traditional" overloads for `operator<<`, they provide also the +"print" function, which uses multiple arguments ("printl" additionally adds the +end-of-line). This is only available in C++11 version. +Alternatively you can also use `ofprint` and `ofprintl` functions with any +`std::ostream` class as first argument, which correspond to the `print` and +`printl` methods respectively. -Iostream support -================ + +# Iostream support This support is not provided by default, but you can use this also with iostream classes' instances. You need to include "`ofmt_iostream.h`" for this. -It provides extra overloads for the internal types `internal::fmt_proxy` and -`internal::fmt_simple_proxy` so that the result of the `fmt` function can be +It provides extra overloads for the internal type +`internal::fmt_proxy_template` so that the result of the `fmt` function can be handled. Note that the rules as above described for `ofmtstream` do not apply here, unless you use `fmt_rawstr()` function for every string. @@ -125,7 +201,7 @@ This provides also a possibility to use `std::put_time` through a special overload of the `fmt` function. Usage: ``` -ofmtbufstream out; +ofmt_bufs out; ... typedef std::chrono::system_clock sclock; std::time_t timenow = sclock::to_time_t(sclock::now()); @@ -133,64 +209,98 @@ std::time_t timenow = sclock::to_time_t(sclock::now()); out << "Timestamp: " << fmt(*std::localtime(&timenow), "%F %T"); ``` -(Note that in C++20 there are some easier way to get from `sclock.now()` to +(Note that in C++20 there are some easier way to get from `sclock::now()` to `std::put_time` and you can easily use it through `fmt` instead of `std::put_time`). -If you want to create a similar formatting specifier for your type, this is -needed to be done: +If you want to create a similar formatting specifier for your type, follow the +example in `ofmt_iostream.h` that defines `snd_time_tm` for `put_time`. +Generally what you need to do is to provide your own structure with defined +`format_send` method inside, and the configuration specification will be +transparently passed by `fmt` to this structure, while returning +`hvu::internal::fmt_proxy_template`. + -1. Create your proxy type that will carry over the value and format -specification. You can also provide an overload for `fmt` function with that -type, although all you need to have is a function that will return the proxy -object by value. +# Stateous API -2. Create an partial specialization of `hvu::internal::fmt_simple_proxy` with -this proxy type. Inside you need to provide a `sendto` method template that -will print this value in the passed stream according to the specification. +Although the stateous API is a biggest design flaw of iostream, there are +cases sometimes, when you need it - especially if you want to use a temporary +buffer for a series of data to be printed. In that case you should use +wrappers of `ofmt_bufx` or `ofmt_refx`. -Follow the example in `ofmt_iostream.h` that defines `tm_proxy` for `put_time`. +The difference between `ofmt_*x` and `ofmt_*s` types is in the way how +the values are sent to the stream by default: +* For `*s` types, the default is to pass it through fmt(), which enforces +default formatting -Stateous API -============ +* For `*x` types, the default is to pass it through fmtx(), which uses the +current formatting -Although the stateous API is a biggest design flaw of iostream, there are -cases sometimes, when you need it - especially if you want to use a tempoarary -buffer of `ofmtbufstream` for a series of data to be printed. In that case -you should use the following: +In any case you can use fmt() of fmtx() to enforce the desired type: + +For example: + +``` +ofmt_bufx ob; +ob.setup(fmtc().hex()); +int x = 0x20, y = 0x30; + +// PRINTS:20 30 +ob << x << " " << y; -* `ofmtbufstream::setup()` to set the formatting specification that should -be permanent -* In case of a need for additional settings at the printed value, use `fmtx` -instead of `fmt` +// PRINTS:32 30 +ob << fmt(x) << " " << y; -The difference between `fmt` and `fmtx` is that the latter applies the -settings to the very stream, on which it operates (rather than on a temporarily -created sub-buffer as `fmt` does), which makes every setting permanent, -and all earlier settings are preserved. This way such settings as `width` -can be applied using `fmtx`, as this setting is always being reset to 0 after -printing a value of a standard type. +// PRINTS: 32 30 +ob << fmt(x, fmtc().width(4)) << " " << y; + +// PRINTS: 20 30 +ob << fmtx(x, fmtc().width(4)) << " " << y; +``` +# Formatting application functions -Additional formatting functions -=============================== +The following functions can be used inside the stream sending instruction: -The `fmt` function returns a proxy that should make the value written into the -stream with appropriate format. Besides there are also other formatting tools: +* `fmt`: Applies formatting according to the settings (or default) * `fmtx`: The "stateous" version of fmt -* `fmtcat`: a multi-argument function where formatted versions of the arguments -are glued together and returned as `std::string` +* `fmtm`: The fmt version using ostream manipulators directly * `fmts`: formats the single value (like with `fmt`) and return it as `std::string`; the call to `fmts(value, man1)` is equivalent to calling -`fmtcat(fmt(value, man1))` with just one argument +`ofcat(fmt(value, man1))` with just one argument * `fmt_rawstr`: Turns a string of `std::string` or pointer-length specification into the `internal::fmt_stringview` type, which can be directly handled by the -`operator<<` overload or `print` method of `ofmtbufstream`. This is also provided +`operator<<` overload or `print` method of `ofmt_bufs`. This is also provided for iostream version. +* `fmt_if`: Formats a boolean value according to the direct specification of +the value for "true" and "false" respectively + +* `operator""_SV`: Turns the literal string into the value of +`internal::fmt_stringview`; allows to take the literal's size statically + +* `OFMT_SV`: A preprocessor macro that does the same as `_SV` operator, +available for C++03 + + +# Additional multi-argument functions + +All these functions take multiple arguments, each of them is turned into a +formatted version and they are glued together into a single string value. + +* `ofcat`: a multi-argument function where formatted versions of the arguments +are glued together and returned as `std::string` + +* `ofprint`: first argument is the stream to which arguments will be written + +* `ofprintl`: like `ofprint`, but `std::endl` is added after the last argument + +Note that these functions operate on default settings, and modifications are +only possible on-demand through fmt* functions. + diff --git a/logging/ofmt_iostream.h b/logging/ofmt_iostream.h index 6e9eacde7..404e78d96 100644 --- a/logging/ofmt_iostream.h +++ b/logging/ofmt_iostream.h @@ -46,7 +46,7 @@ inline std::basic_ostream& operator<<( // facilities, but you can pass the string through fmt() and // this way you make a stringview-forwarder and formatting gets // bypassed. -inline std::ostream& operator<<(std::ostream& os, const hvu::internal::fmt_stringview& v) +inline std::ostream& operator<<(std::ostream& os, const hvu::internal::ofmt_stringview& v) { os.write(v.data(), v.size()); return os; @@ -70,8 +70,7 @@ struct snd_time_tm inline internal::fmt_proxy_template fmt(const struct tm& tim, const char* format) { - internal::snd_time_tm p = {format}; - return internal::fmt_proxy_template(tim, p); + return fmt_make_proxy(tim, internal::snd_time_tm {format}); } } diff --git a/srtcore/api.cpp b/srtcore/api.cpp index a945dc180..5f3584434 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -286,7 +286,7 @@ string CUDTUnited::CONID(SRTSOCKET sock) if (int32_t(sock) <= 0) // embraces SRT_INVALID_SOCK, SRT_SOCKID_CONNREQ and illegal negative domain return ""; - return hvu::fmtcat("@", int(sock), ":"); + return hvu::ofcat("@", int(sock), ":"); } bool CUDTUnited::startGarbageCollector() @@ -1377,7 +1377,7 @@ SRTSOCKET CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int* pw_ } CUDTSocket* ls; - SocketKeeper keep_ls = CUDT::keep(); + SocketKeeper keep_ls = CUDT::keep_none(); // We keep the mutex locked for the whole time of instant checks. // Once they pass, extend the life for the scope by SocketKeeper. @@ -1855,7 +1855,7 @@ SRTSOCKET CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, for (size_t i = 0; i < g.m_config.size(); ++i) { HLOGC(aclog.Debug, log << "groupConnect: OPTION @" << sid << " #" << g.m_config[i].so); - error_reason = hvu::fmtcat("group-derived option: #", g.m_config[i].so); + error_reason = hvu::ofcat("group-derived option: #", g.m_config[i].so); ns->core().setOpt(g.m_config[i].so, &g.m_config[i].value[0], (int)g.m_config[i].value.size()); } @@ -3936,7 +3936,7 @@ void CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, const UDP catch (const CUDTException& x) { HLOGC(smlog.Debug, log << "installMuxer: FAILED; removing multiplexer: ERROR #" << x.getErrorCode() - << ": " << x.getErrorMessage() << ": errno=" << x.getErrno() << ": " << hvu::SysStrError(x.getErrno())); + << ": " << x.getErrorMessage() << ": errno=" << x.getErrno() << ": " << hvu::sys_strerror(x.getErrno())); m_mMultiplexer.erase(muxid); throw; } @@ -4218,7 +4218,7 @@ bool CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) CMultiplexer& m = i->second; #if HVU_ENABLE_HEAVY_LOGGING - hvu::ofmtbufstream that_muxer; + hvu::ofmt_bufs that_muxer; that_muxer << "id=" << m.id() << " addr=" << m.selfAddr().str(); #endif diff --git a/srtcore/atomic.h b/srtcore/atomic.h index 6f510beac..a70308a43 100644 --- a/srtcore/atomic.h +++ b/srtcore/atomic.h @@ -157,6 +157,22 @@ class atomic { #endif } + T operator+=(T val) { +#if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) + ScopedLock lg_(mutex_); + const T t = (value_ += val); + return t; +#elif defined(ATOMIC_USE_GCC_INTRINSICS) + return __atomic_add_fetch(&value_, val, __ATOMIC_SEQ_CST); +#elif defined(ATOMIC_USE_MSVC_INTRINSICS) + return msvc::interlocked::add(&value_, val); +#elif defined(ATOMIC_USE_CPP11_ATOMIC) + return value_ += val; +#else + #error "Implement Me." +#endif + } + /// @brief Performs an atomic decrement operation (value - 1). /// @returns The new value of the atomic object. T operator--() { diff --git a/srtcore/atomic_clock.h b/srtcore/atomic_clock.h index 840d35252..9fc4ac148 100644 --- a/srtcore/atomic_clock.h +++ b/srtcore/atomic_clock.h @@ -29,7 +29,7 @@ class AtomicDuration AtomicDuration() ATR_NOEXCEPT : dur(0) {} - duration_type load() + duration_type load() const { int64_t val = dur.load(); return duration_type(val); diff --git a/srtcore/atomic_msvc.h b/srtcore/atomic_msvc.h index 13d7a40e5..c46b8aac1 100644 --- a/srtcore/atomic_msvc.h +++ b/srtcore/atomic_msvc.h @@ -38,6 +38,7 @@ extern "C" { short _InterlockedIncrement16(short volatile*); long _InterlockedIncrement(long volatile*); __int64 _InterlockedIncrement64(__int64 volatile*); +long InterlockedAdd(long volatile *, long ); short _InterlockedDecrement16(short volatile*); long _InterlockedDecrement(long volatile*); @@ -57,6 +58,7 @@ __int64 _InterlockedCompareExchange64(__int64 volatile*, __int64, __int64); // Define which functions we want to use as inline intriniscs. #pragma intrinsic(_InterlockedIncrement) #pragma intrinsic(_InterlockedIncrement16) +#pragma intrinsic(_InterlockedAdd) #pragma intrinsic(_InterlockedDecrement) #pragma intrinsic(_InterlockedDecrement16) @@ -183,6 +185,11 @@ struct interlocked_imp { return static_cast(_InterlockedExchange( reinterpret_cast(x), static_cast(new_val))); } + + static inline T add(T volatile* x, const T val) { + return static_cast(_InterlockedAdd( + reinterpret_cast(x), static_cast(val))); + } }; template diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index ab8b79cb8..62b1dcaee 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -166,7 +166,7 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(UnitHandle& unit, int32_t muxid) IF_RCVBUF_DEBUG(scoped_log.ss << " returns -2"); return InsertInfo(InsertInfo::BELATED); } - IF_HEAVY_LOGGING(string debug_source = fmtcat("insert %", seqno)); + IF_HEAVY_LOGGING(string debug_source = ofcat("insert %", seqno)); if (offset >= COff(capacity())) { @@ -505,7 +505,7 @@ std::pair CRcvBuffer::dropUpTo(int32_t seqno) } if (!m_tsbpd.isEnabled() && m_bMessageAPI) updateFirstReadableNonOrder(); - IF_HEAVY_LOGGING(debugShowState(fmtcat("drop %", seqno).c_str())); + IF_HEAVY_LOGGING(debugShowState(ofcat("drop %", seqno).c_str())); return std::make_pair(iNumDropped, iNumDiscarded); } @@ -1816,7 +1816,7 @@ std::string CRcvBuffer::Entry::debug() if (int(status) >= 0 && int(status) <= 3) stat = state_names[status]; - return fmtcat(stat, ": ", pUnit ? pUnit->m_Packet.Info() : string("(no packet)")); + return ofcat(stat, ": ", pUnit ? pUnit->m_Packet.Info() : string("(no packet)")); } } // namespace srt diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 97eb9f7c9..2012edda9 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -349,56 +349,20 @@ int32_t CSndBuffer::getMsgNoAtSeq(const int32_t seqno) return m_Packets[offset].getMsgSeq(); } -bool CSndBuffer::getPacketRangeSize(int32_t seqlo, int32_t seqhi, int& w_packets, int& w_bytes) +CSndPacketInfo CSndBuffer::getPacketInfo(int32_t seqno) { ScopedLock bufferguard(m_BufLock); - int offset_lo = CSeqNo::seqoff(m_iSndLastDataAck, seqlo); - int offset_hi = CSeqNo::seqoff(m_iSndLastDataAck, seqhi); - - w_packets = 0; - w_bytes = 0; - - if (offset_hi < 0 || offset_hi < offset_lo) - { - HLOGC(bslog.Debug, log << "getPacketRangeSize: invalid seq range %(" << seqlo << "-" << seqhi - << ") map to off=(" << offset_lo << ", " << offset_hi << ")"); - return false; - } - - // Rule out empty packets case, not sure if possible, but still - if (m_Packets.empty()) - { - // Treat this as false because if the buffer is empty, - // the sequence numbers definitely don't refer to any existing - // packets in the buffer. - return false; - } - - bool full_range = true; - if (offset_lo < 0) - { - offset_lo = 0; - full_range = false; - } - - if (offset_hi >= int(m_Packets.size())) - { - offset_hi = m_Packets.size() - 1; - full_range = false; - } + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); - int npackets = offset_hi - offset_lo + 1, - nbytes = 0; - for (int i = offset_lo; i <= offset_hi; ++i) + if (offset < 0 || m_Packets.empty() || offset >= int(m_Packets.size())) { - nbytes += m_Packets[i].m_iLength; + CSndPacketInfo fail; + fail.seqno = SRT_SEQNO_NONE; + return fail; } - w_packets = npackets; - w_bytes = nbytes; - - return full_range; + return m_Packets[offset].info(); } // XXX Likely unused (left for the use by tests) @@ -538,12 +502,15 @@ int CSndBuffer::extractFirstRexmitPacket(const duration& min_rexmit_interval, in // 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. + + time_point now = steady_clock::now(); + 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); + offset = m_Packets.extractFirstLoss(min_rexmit_interval, now); // No loss found - return 0: no lost packets extracted. if (offset == -1) @@ -591,6 +558,30 @@ int CSndBuffer::extractFirstRexmitPacket(const duration& min_rexmit_interval, in return payload; } +// This function should return the first loss +pair CSndBuffer::peekRexmit(const duration& min_rexmit_interval, time_point& w_send_time) +{ + ScopedLock bufferguard(m_BufLock); + + int seq = SRT_SEQNO_NONE; + + HLOGC(qslog.Debug, log << "REXMIT: looking for loss report since %" << m_iSndLastDataAck << "..."); + + time_point now = steady_clock::now(); + + pair offs = m_Packets.peekLoss(min_rexmit_interval, now, (w_send_time)); + + // No loss found - return 0: no lost packets extracted. + if (offs.first == -1) + { + HLOGC(qslog.Debug, log << "REXMIT: no loss found"); + return make_pair(seq, seq); + } + + seq = CSeqNo::incseq(m_iSndLastDataAck, offs.first); + return make_pair(seq, offs.second == -1 ? SRT_SEQNO_NONE : CSeqNo::incseq(m_iSndLastDataAck, offs.second)); +} + void CSndBuffer::releasePacket(int32_t seqno) { ScopedLock bufferguard(m_BufLock); @@ -630,6 +621,8 @@ void CSndBuffer::releasePacket(int32_t seqno) } } IF_HEAVY_LOGGING(if (!logged) LOGC(bslog.Debug, log << "CSndBuffer::releasePacket: %" << seqno << ": non+ pkts revoked")); + + updAvgBufSize(steady_clock::now()); } CSndBuffer::RevokeStatus CSndBuffer::revoke(int32_t seqno) @@ -693,13 +686,13 @@ void CSndBuffer::removeLossUpTo(int32_t seqno) m_Packets.remove_loss(offset); } -int CSndBuffer::insertLoss(int32_t seqlo, int32_t seqhi, const time_point& pt) +int CSndBuffer::insertLoss(int32_t seqlo, int32_t seqhi, const time_point& pt, time_point& w_sendtime) { 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); + return m_Packets.insert_loss(offset_lo, offset_hi, is_zero(pt) ? steady_clock::now(): pt, (w_sendtime)); } int CSndBuffer::getLossLength() @@ -841,7 +834,7 @@ CSndBuffer::~CSndBuffer() string CSndBuffer::show(int32_t lastsent_seqno) const { using namespace hvu; - ofmtbufstream out; + ofmt_bufs out; int minw = 2; if (m_Packets.size() > 99) @@ -876,6 +869,19 @@ string CSndBuffer::show(int32_t lastsent_seqno) const // SndPktArray implementation +int SndPktArray::loss_record_end(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); + + return current_loss + p.m_iLossLength; +} + int SndPktArray::next_loss(int current_loss) { if (current_loss == -1) @@ -1139,7 +1145,7 @@ SndPktArray::~SndPktArray() m_PktQueue.clear(); } -bool SndPktArray::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, time_point& w_ret_first_time) { // Can't install loss to an empty container if (m_PktQueue.empty()) @@ -1183,6 +1189,7 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne { // Add just one record and mark in both. SndPktArray::Packet& p = m_PktQueue[offset_lo]; + w_ret_first_time = p.m_tsOriginTime; p.m_iNextLossGroupOffset = 0; p.m_iLossLength = loss_length; m_iFirstRexmit = m_iLastRexmit = offset_lo; @@ -1279,6 +1286,7 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne string msg SRT_ATR_UNUSED; SRT_ASSERT(validateLossIntegrity((msg))); + w_ret_first_time = m_PktQueue[offset_lo].m_tsOriginTime; m_iLossLengthCache = m_iLossLengthCache + extra_length; return true; } @@ -1338,6 +1346,7 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne string msg SRT_ATR_UNUSED; SRT_ASSERT(validateLossIntegrity((msg))); + w_ret_first_time = m_PktQueue[offset_lo].m_tsOriginTime; m_iLossLengthCache = m_iLossLengthCache + added_length; return true; } @@ -1367,6 +1376,7 @@ bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& ne // The inserted records completely overlap with the existing ones, // so no changes are necessary, except updating the retransmission time. update_next_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + w_ret_first_time = m_PktQueue[offset_lo].m_tsOriginTime; return true; } @@ -1451,6 +1461,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. update_next_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + w_ret_first_time = m_PktQueue[offset_lo].m_tsOriginTime; string msg SRT_ATR_UNUSED; SRT_ASSERT(validateLossIntegrity((msg))); return true; @@ -1508,7 +1519,7 @@ bool SndPktArray::validateLossIntegrity(std::string& w_message) if (p.m_iNextLossGroupOffset != 0 || p.m_iLossLength != 0) { - w_message += hvu::fmtcat("Non-node element ", i, " has wrong data; "); + w_message += hvu::ofcat("Non-node element ", i, " has wrong data; "); } } } @@ -1518,7 +1529,7 @@ bool SndPktArray::validateLossIntegrity(std::string& w_message) // Now trace everything since the beginning, using states. PacketShowState st; - hvu::ofmtbufstream os; + hvu::ofmt_bufs os; int last_node = m_iFirstRexmit; for (size_t i = 0; i < m_PktQueue.size(); ++i) @@ -1633,7 +1644,7 @@ bool SndPktArray::validateLossIntegrity(std::string& w_message) } -int SndPktArray::extractFirstLoss(const duration& miniv) +int SndPktArray::extractFirstLoss(const duration& miniv, const time_point& now) { // No loss at all if (m_iFirstRexmit == -1) @@ -1649,8 +1660,6 @@ int SndPktArray::extractFirstLoss(const duration& miniv) // should be also marked "zombie" (none found is also possible). int stop_revoke = -1; - time_point now = steady_clock::now(); - int last_cleared = -1; bool skipped_too_new = false; @@ -1707,11 +1716,108 @@ int SndPktArray::extractFirstLoss(const duration& miniv) return -1; } +// Special version for scheduled sending: peek the next candidate +// and the follower, if found. Still do remove all outdated loss +// entries, skip too early entries etc. +pair SndPktArray::peekLoss(const duration& miniv, const time_point& now, time_point& w_send_time) +{ + // No loss at all + if (m_iFirstRexmit == -1) + return make_pair(-1, -1); + + // Exactly one packet available (we believe that "zombies" + // can only follow at least one remaining loss record, so it's not possible + // if only one loss record is available + if (m_iLossLengthCache == 1) + { + // We have exactly one packet; report it, regardless of conditions. + return make_pair(m_iFirstRexmit, -1); + } + + // Note that records may have: + // - "zombie": marked for non-eligible by cleared time - remove if possible + // - "too new": such time that time + miniv > now() - this record must remain + // + // If during the search there was no "too new" record found yet, remove + // every "zombie" on the way, and also the extracted record. If any "too + // new" was found, do not remove anything anymore and the qualified record + // should be also marked "zombie" (none found is also possible). + int stop_revoke = -1; + int last_cleared = -1; + bool skipped_too_new = false; + + int off_planned = -1, off_next = -1; + + // Try to find the first one with skipping all "too early" ones. + for (int loss_begin = m_iFirstRexmit; loss_begin != -1; loss_begin = next_loss(loss_begin)) + { + int loss_end = loss_begin + m_PktQueue[loss_begin].m_iLossLength; + + for (int i = loss_begin; i != loss_end; ++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. + if (!p.updated_rexmit_time_passed(now, miniv)) + { + if (stop_revoke == -1 && i > 0) + stop_revoke = i - 1; + skipped_too_new = true; + HLOGC(qslog.Debug, log << "... skipped +" << i << " - too early by " + << FormatDurationAuto(now + miniv - p.m_tsNextRexmitTime)); + continue; + } + + // --- found --- + off_planned = i; + // Check the next loss + off_next = i + 1; + if (off_next == loss_end) + { + // Worst case, it will turn back to -1. + off_next = next_loss(off_planned); + } + goto Break2; // break 2 loops + } + else // Zombie + { + HLOGC(qslog.Debug, log << "... skipped +" << i << " - cleared earlier"); + if (!skipped_too_new) + last_cleared = i; + } + // If it was cleared, continue searching. + } + } + +Break2: + + if (last_cleared != -1) + { + remove_loss(last_cleared); + } + + // If not found any "old enough" loss record, simply return + // the first and second loss you found. We know that there + // at least 2 loss reports. + if (off_planned == -1) + { + off_planned = m_iFirstRexmit; + off_next = off_planned + 1; + if (off_next >= loss_record_end(m_iFirstRexmit)) + off_next = next_loss(m_iFirstRexmit); + } + + w_send_time = m_PktQueue[off_planned].m_tsOriginTime; + + return make_pair(off_planned, off_next); +} + // Debug support string SndPktArray::show_external(int32_t seqno, int32_t lastsent_seqno) const { using namespace hvu; - ofmtbufstream out; + ofmt_bufs out; int minw = 2; if (size() > 99) @@ -1741,7 +1847,7 @@ string SndPktArray::show_external(int32_t seqno, int32_t lastsent_seqno) const return out.str(); } -void SndPktArray::showline(int index, int unique_index, PacketShowState& st, hvu::ofmtbufstream& out) const +void SndPktArray::showline(int index, int unique_index, PacketShowState& st, hvu::ofmt_bufs& out) const { const Packet& p = m_PktQueue[index]; diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index f60029bd5..23eaefd00 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -66,6 +66,17 @@ namespace srt { class CSndBuffer; +// Copy of the below data to be extracted under a lock +struct CSndPacketInfo +{ + int32_t seqno; + int32_t msgfield; + size_t size; + sync::steady_clock::time_point send_time; + sync::steady_clock::time_point rexmit_time; + sync::steady_clock::duration ttl; +}; + struct CSndBlock { typedef sync::steady_clock::time_point time_point; @@ -91,6 +102,15 @@ struct CSndBlock int64_t elapsed_ms = sync::count_milliseconds(since - m_tsOriginTime); return elapsed_ms > m_iTTL; } + + CSndPacketInfo info() const + { + CSndPacketInfo out = { + m_iSeqNo, m_iMsgNoBitset, size_t(m_iLength), + m_tsOriginTime, m_tsRexmitTime, sync::milliseconds_from(m_iTTL) + }; + return out; + } }; struct SndPktArray @@ -190,7 +210,7 @@ struct SndPktArray bool clear_loss(int index); - bool insert_loss(int ixlo, int ixhi, const time_point& nowtime = sync::steady_clock::now()); + bool insert_loss(int ixlo, int ixhi, const time_point& nowtime, time_point& w_ret_first_time); void update_next_rexmit_time(int ixlo, int ixhi, const time_point& time) { @@ -203,10 +223,11 @@ struct SndPktArray } int next_loss(int current_loss); - + int loss_record_end(int current_loss); int loss_length() const { return m_iLossLengthCache; } - int extractFirstLoss(const duration& min_interval = duration()); + int extractFirstLoss(const duration& min_interval, const time_point& now); + std::pair peekLoss(const duration& miniv, const time_point& now, time_point& w_send_time); size_t size() const { @@ -272,7 +293,7 @@ struct SndPktArray PacketShowState(): remain_loss_group(0), next_loss_begin(-1) {} }; - void showline(int index, int uniaue_index, PacketShowState& st, hvu::ofmtbufstream& out) const; + void showline(int index, int uniaue_index, PacketShowState& st, hvu::ofmt_bufs& out) const; std::string show_external(int32_t seqno, int32_t lastsent_seqno = SRT_SEQNO_NONE) const; @@ -406,6 +427,9 @@ class CSndBuffer 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); + SRT_TSA_NEEDS_NONLOCKED(m_BufLock) + std::pair peekRexmit(const duration& min_rexmit_interval, time_point& w_send_time); + private: SRT_TSA_NEEDS_LOCKED(m_BufLock) int readPacketInternal(int offset, CSndPacket& w_packet, time_point& w_origintime, DropRange& w_drop); @@ -453,7 +477,9 @@ class CSndBuffer int getAvgBufSize(int& bytes, int& timespan); - bool getPacketRangeSize(int32_t seqlo, int32_t seqhi, int& w_packets, int& w_bytes); + // bool getPacketRangeSize(int32_t seqlo, int32_t seqhi, int& w_packets, int& w_bytes); + CSndPacketInfo getPacketInfo(int32_t seqno); + int getCurrBufSize(int& bytes, int& timespan) const { sync::ScopedLock lk (m_BufLock); @@ -490,7 +516,7 @@ 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()); + int insertLoss(int32_t lo, int32_t hi, const sync::steady_clock::time_point& pt, sync::steady_clock::time_point& w_first_send_time); // For testing purposes only. Not used in the code. int32_t popLostSeq(DropRange&); diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp index 2faf36225..e1cfa4fff 100644 --- a/srtcore/channel.cpp +++ b/srtcore/channel.cpp @@ -271,7 +271,7 @@ void CChannel::createSocket(int family) { LOGC(kmlog.Error, log << "::setsockopt: failed to set IPPROTO_IPV6/IPV6_V6ONLY = " << m_mcfg.iIpV6Only << ": " - << SysStrError(NET_ERROR)); + << sys_strerror(NET_ERROR)); } } } @@ -476,7 +476,7 @@ void CChannel::setUDPSockOpt() { if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) { - LOGC(kmlog.Error, log << "setsockopt(IP_TTL): " << SysStrError(NET_ERROR)); + LOGC(kmlog.Error, log << "setsockopt(IP_TTL): " << sys_strerror(NET_ERROR)); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } is_set = true; @@ -491,7 +491,7 @@ void CChannel::setUDPSockOpt() if (-1 == ::setsockopt( m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) { - LOGC(kmlog.Error, log << "setsockopt(IPV6_UNICAST_HOPS): " << SysStrError(NET_ERROR)); + LOGC(kmlog.Error, log << "setsockopt(IPV6_UNICAST_HOPS): " << sys_strerror(NET_ERROR)); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } is_set = true; @@ -501,7 +501,7 @@ void CChannel::setUDPSockOpt() { if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) { - LOGC(kmlog.Error, log << "setsockopt(IP_TTL): " << SysStrError(NET_ERROR) + LOGC(kmlog.Error, log << "setsockopt(IP_TTL): " << sys_strerror(NET_ERROR) << fmt_alt(adr_unspec, " (v6 unspec)", " (v6 mapped v4)")); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } @@ -524,7 +524,7 @@ void CChannel::setUDPSockOpt() { if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) { - LOGC(kmlog.Error, log << "setsockopt(IP_TOS): " << SysStrError(NET_ERROR)); + LOGC(kmlog.Error, log << "setsockopt(IP_TOS): " << sys_strerror(NET_ERROR)); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } is_set = true; @@ -542,7 +542,7 @@ void CChannel::setUDPSockOpt() if (-1 == ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) { - LOGC(kmlog.Error, log << "setsockopt(IPV6_TCLASS): " << SysStrError(NET_ERROR)); + LOGC(kmlog.Error, log << "setsockopt(IPV6_TCLASS): " << sys_strerror(NET_ERROR)); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } is_set = true; @@ -554,7 +554,7 @@ void CChannel::setUDPSockOpt() { if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) { - LOGC(kmlog.Error, log << "setsockopt(IP_TOS): " << SysStrError(NET_ERROR) + LOGC(kmlog.Error, log << "setsockopt(IP_TOS): " << sys_strerror(NET_ERROR) << (adr_unspec ? " (v6 unspecified)" : " (v6 mapped v4)") << (using_tclass ? "(fallback to IP_TOS)" : "")); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); @@ -584,7 +584,7 @@ void CChannel::setUDPSockOpt() if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_BINDTODEVICE, m_mcfg.sBindToDevice.c_str(), m_mcfg.sBindToDevice.size())) { - LOGC(kmlog.Error, log << "setsockopt(SRTO_BINDTODEVICE): " << SysStrError(NET_ERROR)); + LOGC(kmlog.Error, log << "setsockopt(SRTO_BINDTODEVICE): " << sys_strerror(NET_ERROR)); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } } @@ -1070,7 +1070,7 @@ EReadStatus CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet) const } else { - HLOGC(krlog.Debug, log << CONID() << "(sys)recvmsg: " << SysStrError(err) << " [" << err << "]"); + HLOGC(krlog.Debug, log << CONID() << "(sys)recvmsg: " << sys_strerror(err) << " [" << err << "]"); status = RST_ERROR; } @@ -1141,7 +1141,7 @@ EReadStatus CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet) const const int err = NET_ERROR; if (std::find(fatals, fatals_end, err) != fatals_end) { - HLOGC(krlog.Debug, log << CONID() << "(sys)WSARecvFrom: " << SysStrError(err) << " [" << err << "]"); + HLOGC(krlog.Debug, log << CONID() << "(sys)WSARecvFrom: " << sys_strerror(err) << " [" << err << "]"); status = RST_ERROR; } else @@ -1216,7 +1216,7 @@ EReadStatus CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet) const HLOGC(krlog.Debug, log << CONID() << "NET ERROR: packet size=" << recv_size << " msg_flags=0x" - << fmt(msg_flags, hex) + << fmtm(msg_flags, hex) << ", detected flags:" << flg.str()); #endif status = RST_AGAIN; diff --git a/srtcore/common.cpp b/srtcore/common.cpp index f48260c3e..d9155dd1f 100644 --- a/srtcore/common.cpp +++ b/srtcore/common.cpp @@ -252,7 +252,7 @@ void CIPAddress::decode(const uint32_t (&ip)[4], const sockaddr_any& peer, socka #if HVU_ENABLE_LOGGING using namespace hvu; - ofmtbufstream peeraddr_form; + ofmt_bufs peeraddr_form; fmtc hex04 = fmtc().hex().fillzero().width(4); peeraddr_form << fmt(peeraddr16[0], hex04); for (int i = 1; i < 8; ++i) @@ -272,7 +272,7 @@ void CIPAddress::decode(const uint32_t (&ip)[4], const sockaddr_any& peer, socka } -static inline void PrintIPv4(uint32_t aval, hvu::ofmtbufstream& os) +static inline void PrintIPv4(uint32_t aval, hvu::ofmt_bufs& os) { typedef Bits<8+8+8+7, 8+8+8> q0; typedef Bits<8+8+7, 8+8> q1; @@ -292,7 +292,7 @@ std::string CIPAddress::show(const uint32_t (&ip)[4]) using namespace hvu; - ofmtbufstream out; + ofmt_bufs out; if (is_mapped_ipv4) { out << "::FFFF:"; diff --git a/srtcore/common.h b/srtcore/common.h index 7d55b79ce..e985ef348 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -161,16 +161,14 @@ struct CNetworkInterface std::string str() const { - return hvu::fmtcat(address.str(), "/", interface_index); + return hvu::ofcat(address.str(), "/", interface_index); } }; #if ENABLE_HEAVY_LOGGING inline std::string RecordLocation(const char* file, int line) { - std::ostringstream out; - out << file << ":" << line; - return out.str(); + return hvu::ofcat(file, ":", line); } #else inline std::string RecordLocation(const char*, int) { return std::string(); } @@ -1037,7 +1035,7 @@ struct CIPAddress static void encode(const struct sockaddr_any& addr, uint32_t (&ip)[4]); static void decode(const uint32_t (&ip)[4], const sockaddr_any& peer, sockaddr_any& w_addr); - // NOTE: This function could return hvu::ofmtbufstream, but the enclosed + // NOTE: This function could return hvu::ofmt_bufs, but the enclosed // std::stringstream is not copyable before C++11. static std::string show(const uint32_t (&ip)[4]); }; @@ -1603,7 +1601,7 @@ inline int32_t SrtParseVersion(const char* v) inline std::string SrtVersionString(int version) { - hvu::ofmtbufstream out; + hvu::ofmt_bufs out; int patch = version % 0x100; int minor = (version/0x100)%0x100; diff --git a/srtcore/congctl.cpp b/srtcore/congctl.cpp index 57c3aec32..33b6b8fdd 100644 --- a/srtcore/congctl.cpp +++ b/srtcore/congctl.cpp @@ -631,6 +631,19 @@ SrtCongestion::NamePtr SrtCongestion::congctls[N_CONTROLLERS] = {"file", Creator::Create } }; +bool SrtCongestion::select_sendmode(int sendmode) +{ + // 0 should be handled already, but just follow it. + if (sendmode > 0) + { + selector = 1; // "file" CC + } + else + { + selector = 0; // "live" (normal live mode) + } + return true; +} bool SrtCongestion::configure(CUDT* parent) { diff --git a/srtcore/congctl.h b/srtcore/congctl.h index 2bdf7a9ed..f4d9de648 100644 --- a/srtcore/congctl.h +++ b/srtcore/congctl.h @@ -80,6 +80,19 @@ class SrtCongestion return true; } + bool select(const std::string& name, int sendmode) + { + // Ok, if sendmode is 0, allow selection by name. + // Otherwise enforce "live" mode by all means, but select + // the "file" CC. + if (sendmode == 0) + return select(name); + + return select_sendmode(sendmode); + } + + bool select_sendmode(int sendmode); + std::string selected_name() { if (selector == N_CONTROLLERS) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 44ec31163..46cdba479 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -2320,8 +2320,8 @@ int CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t bytelen, uint32_t } LOGC(cnlog.Debug, log << "HSREQ/rcv: cmd=" << SRT_CMD_HSREQ << "(HSREQ) len=" << bytelen - << " vers=0x" << fmt(srtdata[SRT_HS_VERSION], hex) - << " opts=0x" << fmt(srtdata[SRT_HS_FLAGS], hex) + << " vers=0x" << fmtm(srtdata[SRT_HS_VERSION], hex) + << " opts=0x" << fmtm(srtdata[SRT_HS_FLAGS], hex) << " delay=" << SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY])); m_uPeerSrtVersion = srtdata[SRT_HS_VERSION]; @@ -3395,7 +3395,7 @@ bool CUDT::interpretGroup(CUDTSocket* lsn, const int32_t groupdata[], size_t dat log << CONID() << "interpretGroup: STATE: HsSide=" << s_hs_side[m_SrtHsSide] << " HS MSG: " << MessageTypeStr(UMSG_EXT, hsreq_type_cmd) << " $" << grpid << " type=" << gtp << " weight=" << link_weight - << " flags=0x" << fmt(link_flags, hex)); + << " flags=0x" << fmtm(link_flags, hex)); #endif // XXX Here are two separate possibilities: @@ -4873,7 +4873,7 @@ EConnectStatus CUDT::processConnectResponse(const CPacket& response, CUDTExcepti { HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: REQ-TIME LOW; got INDUCTION HS response (cookie:" - << fmt(m_ConnRes.m_iCookie, hex) + << fmtm(m_ConnRes.m_iCookie, hex) << " version:" << m_ConnRes.m_iVersion << "), sending CONCLUSION HS with this cookie"); @@ -6239,12 +6239,12 @@ bool CUDT::frequentLogAllowed(size_t logid, const time_point& tnow, std::string& const int supr = m_aSuppressedMsg[logid]; if (supr > 0) - w_why = fmtcat("++SUPPRESSED: ", supr); + w_why = ofcat("++SUPPRESSED: ", supr); m_aSuppressedMsg[logid] = 0; } else { - w_why = fmtcat("Too early - last one was ", FormatDuration(tnow - m_tsLogSlowDown[logid].load())); + w_why = ofcat("Too early - last one was ", FormatDuration(tnow - m_tsLogSlowDown[logid].load())); // Set YOUR OWN bit, atomically. m_LogSlowDownExpired |= uint8_t(BIT(logid)); ++m_aSuppressedMsg[logid]; @@ -6301,7 +6301,7 @@ SRT_REJECT_REASON CUDT::setupCC() // SrtCongestion will retrieve whatever parameters it needs // from *this. - bool res = m_CongCtl.select(m_config.sCongestion.str()); + bool res = m_CongCtl.select(m_config.sCongestion.str(), m_config.uSenderMode); if (!res || !m_CongCtl.configure(this)) { return SRT_REJ_CONGESTION; @@ -7083,20 +7083,36 @@ int CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) << " !" << BufferStamp(data, size)); time_point start_time = m_stats.tsStartTime; - if (w_mctrl.srctime && w_mctrl.srctime < count_microseconds(start_time.time_since_epoch())) + if (w_mctrl.srctime) { - LOGC(aslog.Error, - log << CONID() << "Wrong source time was provided. Sending is rejected."); - throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI); - } + // Start time can't be earlier than connection time; this kind of time + // wouldn't be able to be handled and it's likely a bug. + if (w_mctrl.srctime < count_microseconds(start_time.time_since_epoch())) + { + LOGC(aslog.Error, + log << CONID() << "Wrong source time was provided. Sending is rejected."); + throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI); + } - if (w_mctrl.srctime && (!m_config.bMessageAPI || !m_bTsbPd)) + /* XXX Check again. When using file mode at the reception side, the + timestamp is ignored anyway, so this is likely not necessary. + if ((!m_config.bMessageAPI || !m_bTsbPd)) + { + HLOGC(aslog.Warn, log << CONID() + << "Source time can only be used with TSBPD and Message API enabled. Using default time instead."); + w_mctrl.srctime = 0; + } + */ + } + else { - HLOGC( - aslog.Warn, - log << CONID() - << "Source time can only be used with TSBPD and Message API enabled. Using default time instead."); - w_mctrl.srctime = 0; + // SRCTIME is undefined - this is not acceptable in STOW sender modes + if (m_config.uSenderMode != 0) + { + LOGC(aslog.Error, log << CONID() << "sendmsg2: with sender mode=" << m_config.uSenderMode + << " declaring packet send time is obligatory"); + throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI); + } } // w_mctrl.seqno is INPUT-OUTPUT value: @@ -7138,10 +7154,8 @@ int CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) } else { - if (m_LastSched.kickSchedule(seqno, steady_clock::now(), m_tdSendInterval)) - { - m_pMuxer->scheduleSend(m_parent, seqno, sched::TP_REGULAR, m_LastSched.lastTime()); - } + time_point dst_time = time_point() + microseconds_from(w_mctrl.srctime); + scheduleRegular(seqno, steady_clock::now(), dst_time); } #ifdef SRT_ENABLE_ECN @@ -7157,6 +7171,145 @@ int CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) return size; } +// [[affinity(main)]] +void CUDT::scheduleRegular(int32_t seqno, const time_point& currtime, const time_point& exp_sendtime) +{ + m_LastSched.lazyStart(seqno, currtime); + time_point delivery = exp_sendtime + milliseconds_from(m_iPeerTsbPdDelay_ms) + slippedRTT(2); + m_pMuxer->scheduleSend(m_parent, seqno, sched::TP_REGULAR, exp_sendtime, delivery); +} + +void CUDT::scheduleRexmit(int32_t seqno, const time_point& exp_sendtime) +{ + time_point delivery = exp_sendtime + milliseconds_from(m_iPeerTsbPdDelay_ms) + slippedRTT(2); + m_pMuxer->scheduleSend(m_parent, seqno, sched::TP_REXMIT, exp_sendtime, delivery); +} + + +steady_clock::time_point CSendPipeManager::updateNext(const steady_clock::time_point& currtime, + const steady_clock::duration& send_interval) +{ + duration passed_time = currtime - m_tsTime.load(); + + int deviation = count_microseconds(send_interval - passed_time); + m_tdAverageDeviation_us = avg_iir_lazy<16>(m_tdAverageDeviation_us, deviation); + + time_point nextperiod_time = m_tsTime.load() + send_interval; + time_point forfeit_time = currtime - send_interval * 2; + + HLOGC(qslog.Debug, log << "PLAN: now=" << FormatTime(currtime) + << " LAST:" << FormatDurationAuto(m_tsTime.load() - currtime, true) + << " NEXT:" << FormatDurationAuto(nextperiod_time - currtime, true) + << " PAST-LAST: " << FormatDurationAuto(forfeit_time - nextperiod_time, true)); + + if (nextperiod_time + seconds_from(1) < currtime) + nextperiod_time = forfeit_time; + + if (nextperiod_time < currtime) + { + nextperiod_time = currtime; + } + // Remember this as the last sending time + // NOTE: We remember this as the last sending time, + // regardless if we have used the time override or not. + // Time override is used when a regular packet's expected sending + // time is earlier than nextperiod_time. In this case we send it + // faster than according to the current speed, but we still remember + // this time as the last sent in order to keep up with the speed. + m_tsTime = nextperiod_time; + + return m_tsTime; +} + +// [[affinity(CSndQueue::m_WorkerThread)]] +bool CUDT::planSendingTime(sched::Type type, const CUDT::time_point& /* UNUSED latest_delivery*/, CUDT::time_point& w_sendtime) +{ + // type: check if TP_REXMIT, otherwise it's regular + // latest_delivery: the latest possible time when it should be delivered + // w_sendtime: + // [IN] The PLANNED time for the packet (in eager mode, the latest time to send the regular packet) + // [ONT] The resulting time when the packet should be sent + // + // Return: + // * true: this scheduled packet shall be sent + // * false: please drop this send request + + // Ok, the state update rules: + // + // m_tsTime is the time when the packet was sent last time. + // This should be set to the time written into w_sendtime. + // All calculations start from this time. It should not be zero any time. + + time_point currtime = steady_clock::now(); + + // Could assert, but let's just do a quick check + if (m_LastSched.updateFirst(currtime)) + { + HLOGC(qslog.Debug, log << "PLAN: First time, set as NOW: " << FormatTime(m_LastSched.lastTime())); + + // Yes, will be in the past during pickup; doesn't matter. + w_sendtime = m_LastSched.lastTime(); + return true; + } + + time_point nextperiod_time = m_LastSched.updateNext(currtime, m_tdSendInterval); + + // Ok, so nextperiod_time is now the time that should be set + // as w_sendtime; we need to check it if it's in forward. + // If so, take it as a good deal. + if (nextperiod_time <= w_sendtime) + { + HLOGC(qslog.Debug, log << "PLAN: ... PLANTIME=" << FormatTime(w_sendtime) << " - speedup by " + << FormatDurationAuto(w_sendtime - nextperiod_time)); + + w_sendtime = nextperiod_time; + return true; + } + + // Ok, now we are past the intended sending time. + // Additional rules: + + // For regular (first-time, unique) packets, we allow + // for overdrive, no matter how fast it would get. + + if (type != sched::TP_REXMIT) + { + HLOGC(qslog.Debug, log << "PLAN: ... PLANTIME=" << FormatTime(w_sendtime) << " KEPT - override NEXTSEND by " + << FormatDurationAuto(nextperiod_time - w_sendtime)); + // REGULAR + // Leave the send time as is, while keeping the last + // sending time as the alleged next sending time. + return true; + } + + /* XXX Block this for now. + This also means that planning the packet can't be rejected. + This is still only a plan - actual sending will happen only + when the packet is about to be sent and it's requested from + the Socket to provide it. + + // For retransmitted packets, qualify them to send at + // this predicted time. Except they are already past the + // latest possible delivery time, in which case deny them. + + if (nextperiod_time > latest_delivery) + { + HLOGC(qslog.Debug, log << "PLAN:... REXMIT nextsend=" << FormatTime(nextperiod_time) + << " exceeds latest delivery by " << FormatDurationAuto(nextperiod_time - latest_delivery) + << " - NOT PLANNING"); + return false; + } + // */ + + // Condition: next delivery time is past w_sendtime, but still before latest_delivery + // Plan regularly, without override. + + HLOGC(qslog.Debug, log << "PLAN:... REXMIT nextsend=" << FormatTime(nextperiod_time) + << " - PLANNING at this time; " << FormatDurationAuto(currtime - nextperiod_time) << " towards NOW"); + w_sendtime = nextperiod_time; + return true; +} + void CUDT::updateCryptoOnSending() { int keyindex[2] = {-1, -1}; @@ -7180,21 +7333,6 @@ void CUDT::updateCryptoOnSending() } } -sync::steady_clock::time_point 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. - // XXX Notify in the log that there was 1 second of time forgotten. - return now; - } - - return last + m_tdSendInterval.load(); -} - int CUDT::recv(char* data, int len) { SRT_MSGCTRL mctrl = srt_msgctrl_default; @@ -8770,7 +8908,7 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point // included, but it also triggers for any other kind of invalid value. // This check MUST BE DONE before making any operation on this number. LOGC(inlog.Error, log << CONID() << "ACK: IPE/EPE: received invalid ACK value: " << ackdata_seqno - << " " << fmt(ackdata_seqno, hex) << " (IGNORED)"); + << " " << fmtm(ackdata_seqno, hex) << " (IGNORED)"); return; } @@ -9135,6 +9273,8 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) // If remain with this value, there's nothing to schedule. int32_t sched_lo = SRT_SEQNO_NONE, sched_hi = SRT_SEQNO_NONE; + time_point first_send_time; + { #if SRT_ENABLE_BONDING // Keep the group from disappearing in the meantime @@ -9194,11 +9334,11 @@ 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, steady_clock::now()); + num = m_pSndBuffer->insertLoss(losslist_lo, losslist_hi, steady_clock::now(), (first_send_time)); sched_lo = losslist_lo; sched_hi = losslist_hi; } - // ELSE losslist_lo %< m_iSndLastAck + // ELSE losslist_lo <% m_iSndLastAck else { // This should be theoretically impossible because this would mean that @@ -9220,7 +9360,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, steady_clock::now()); + num = m_pSndBuffer->insertLoss(m_iSndLastAck, losslist_hi, steady_clock::now(), (first_send_time)); dropreq_hi = CSeqNo::decseq(m_iSndLastAck); IF_HEAVY_LOGGING(drop_type = "partially"); @@ -9265,7 +9405,8 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding %" << losslist[i] << " (1 packet) to loss list"); - num = m_pSndBuffer->insertLoss(losslist[i], losslist[i], steady_clock::now()); + num = m_pSndBuffer->insertLoss(losslist[i], losslist[i], steady_clock::now(), (first_send_time)); + sched_lo = sched_hi = losslist[i]; m_StatsLock.lock(); m_stats.sndr.lost.count(num); @@ -9287,34 +9428,15 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) } } - if (m_config.uSenderMode == 1) + if (m_config.uSenderMode != 0 && sched_lo != SRT_SEQNO_NONE) { - int npackets = 0, nbytes = 0; - if (!m_pSndBuffer->getPacketRangeSize(sched_lo, sched_hi, (npackets), (nbytes))) + if (sched_lo != SRT_SEQNO_NONE && !is_zero(first_send_time)) // theoretically can happen in buffer empty - paranoid check { - // XXX LOGC + scheduleRexmit(sched_lo, first_send_time); } - - time_point start; - duration step; - if (npackets && defineSchedTimes(sched_lo, sched_hi, (start), (step))) + else { - 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); + LOGC(qslog.Error, log << "LOSSREPORT: IPE: loss scheduled, but % and time empty!"); } } @@ -9651,7 +9773,7 @@ void CUDT::processCtrl(const CPacket &ctrlpkt) log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=@" << ctrlpkt.id() - << " arg=" << ctrlpkt.getAckSeqNo() << "/0x" << fmt(ctrlpkt.getAckSeqNo(), hex)); + << " arg=" << ctrlpkt.getAckSeqNo() << "/0x" << fmtm(ctrlpkt.getAckSeqNo(), hex)); // XXX [TSA] This function may need to lock m_ConnectionLock switch (ctrlpkt.getType()) @@ -9801,7 +9923,7 @@ void CUDT::updateAfterSrtHandshake(int hsv) { SharedLock glock (uglobal().m_GlobControlLock); grpspec = m_parent->m_GroupOf - ? fmtcat(" group=$", m_parent->m_GroupOf->id()) + ? ofcat(" group=$", m_parent->m_GroupOf->id()) : string(); } #else @@ -9888,6 +10010,44 @@ void CUDT::retransmissionConsumeLength(size_t payload_size) #endif } +int CUDT::extractPlannedLoss(SendTaskProto& proto) +{ + // Ok, first extract the first and next loss. NOTE: + // 1. It doesn't mean that any loss is eligible for sending. + // 2. Having both seqno set only means that there's more than one. + + // All we can do here is to plan sending for the losses that are reported. + + duration delivery_slip = milliseconds_from(m_iPeerTsbPdDelay_ms) + slippedRTT(2); + + time_point sendtime; + pair nextloss = m_pSndBuffer->peekRexmit(minRexmitInterval(), (sendtime)); + + // RETURN: + // 0 - There are no lost packets scheduled, including the current one. + // 1 - The current packet was filled, but there are no more losses. + // 2 - The current was filled, and next, too. + if (nextloss.first == SRT_SEQNO_NONE) + { + // LOG: no loss available + return 0; + } + + // Fill the first one + proto.m_Packet.set_socket(m_parent); + proto.m_Packet.m_iSeqNo = nextloss.first; + proto.m_Packet.m_Type = sched::TP_REXMIT; + proto.m_tsSendTime = sendtime; + proto.m_tsLatestDeliveryTime = sendtime + delivery_slip; + + planSendingTime(sched::TP_REXMIT, proto.m_tsLatestDeliveryTime, (proto.m_tsSendTime)); + + if (nextloss.second == SRT_SEQNO_NONE) + return 1; + + return 2; +} + int CUDT::packLostData(CSndPacket& w_sndpkt) { #ifdef SRT_ENABLE_MAXREXMITBW @@ -10776,7 +10936,7 @@ int CUDT::checkLazySpawnTsbPdThread() HLOGP(qrlog.Debug, "Spawning Socket TSBPD thread"); #if HVU_ENABLE_HEAVY_LOGGING - ofmtbufstream buf; + ofmt_bufs buf; // Take the last 2 ciphers from the socket ID. string s = fmts(m_SocketID, fmtc().fillzero().width(2)); buf << "SRT:TsbPd:@" << s.substr(s.size()-2, 2); @@ -11065,7 +11225,7 @@ bool CUDT::handlePacketDecryption(CPacket& packet) if (rc == ENCS_CLEAR && packet.getMsgCryptoFlags() == EK_NOENC) return true; - IF_LOGGING(failure = fmtcat("Decryption ", rc == ENCS_FAILED ? "failed" : "unsupported")); + IF_LOGGING(failure = ofcat("Decryption ", rc == ENCS_FAILED ? "failed" : "unsupported")); } // DECRYPTION FAILED: Just display the error in the logs and update stats. @@ -11192,7 +11352,7 @@ bool CUDT::handleGroupPacketReception(CUDTGroup* grp, vector 0) { const int32_t csn = m_iSndCurrSeqNo; - const int num = m_pSndBuffer->insertLoss(m_iSndLastAck, csn, steady_clock::now()); + time_point first_send_time; + const int num = m_pSndBuffer->insertLoss(m_iSndLastAck, csn, steady_clock::now(), (first_send_time)); if (num > 0) { m_StatsLock.lock(); @@ -12613,7 +12774,7 @@ void CUDT::checkBlindRexmitTimer(const steady_clock::time_point& currtime) if (m_config.uSenderMode != 0) { - scheduleRexmitRange(m_iSndLastAck, csn); + scheduleRexmit(m_iSndLastAck, first_send_time); } } } @@ -12633,94 +12794,6 @@ void CUDT::checkBlindRexmitTimer(const steady_clock::time_point& currtime) } } -void 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 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 SRT_ATR_UNUSED = CSeqNo::seqlen(lo, hi); - - 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; -} - void CUDT::checkTimers() { updateCC(TEV_CHECKTIMER, EventVariant(TEV_CHT_INIT)); diff --git a/srtcore/core.h b/srtcore/core.h index bcb913594..359e65772 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -152,6 +152,8 @@ class CLastSend sync::atomic m_uNumberBytes; uint32_t m_uBytesPerSecond; +public: + CLastSend(): m_uNumberPackets(0), m_uNumberBytes(0) { } @@ -193,179 +195,65 @@ class CLastSend 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; - + m_uNumberBytes += +bytes; + m_uNumberPackets += +npackets; // Do not calculate speed here. Do it on reset only. } }; -class CLastSched +class CSendPipeManager { friend class CUDT; + typedef sync::steady_clock::time_point time_point; + typedef sync::steady_clock::duration duration; - sync::Mutex m_Lock; + //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; + int m_tdAverageDeviation_us; 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) {} + CSendPipeManager(): + m_iSchedSeqNo(SRT_SEQNO_NONE), + m_tsTime() + { + } 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) + bool lazyStart(int32_t seqno, const sync::steady_clock::time_point& tm) { - 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; + m_iSchedSeqNo = seqno; + m_tsTime = tm; - // Still false because we have just one packet here, - // so once this is scheduled, there's 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.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 - // 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; + return false; } - // 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) + // This should not happen, kinda sanity check + bool updateFirst(const sync::steady_clock::time_point& currtime) { - // 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) + if (is_zero(m_tsTime) || currtime - m_tsTime.load() > sync::seconds_from(1)) { - // 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 + m_tsTime = currtime; + return true; } - - // We do want to schedule this one, so record the data - m_iSchedSeqNo = newseq; - m_tsTime = exec_time; - return true; + return false; } + sync::steady_clock::time_point updateNext(const sync::steady_clock::time_point& currtime, + const sync::steady_clock::duration& send_interval); }; class CUDTUnited; @@ -683,14 +571,17 @@ 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 + + duration slippedRTT(int slip_factor) const { int avgrtt = m_iSRTT; - int slip = 4 * m_iRTTVar; + int slip = slip_factor * m_iRTTVar; // This is mainly to prevent the value from being negative - return sync::microseconds_from(std::max(avgrtt/2, avgrtt - slip)); + return sync::microseconds_from(std::max(avgrtt/2, avgrtt + slip)); } + duration optimisticRTT() const { return slippedRTT(-4); } + SRT_TSA_NEEDS_LOCKED(m_RecvAckLock) int32_t sndSeqNo() const { return m_iSndCurrSeqNo; } int32_t schedSeqNo() const { return m_iSndNextSeqNo; } @@ -1154,8 +1045,16 @@ class CUDT void checkSndTimers(); // For schedule mode sending - sync::steady_clock::time_point calculateRegularSchedTime(); + sync::steady_clock::time_point lastSchedTime() const { return m_LastSched.lastTime(); } + //sync::steady_clock::duration lastSendInterval() const { return m_LastSched.lastInterval(); } + void scheduleRegular(int32_t seqno, const time_point& currtime, const time_point& exp_sendtime); + void scheduleRexmit(int32_t seqno, const time_point& exp_sendtime); + +public: // Used by scheduler + bool planSendingTime(sched::Type type, const CUDT::time_point& latest_delivery, CUDT::time_point& w_sendtime); + int extractPlannedLoss(SendTaskProto& proto); +private: /// @brief Check and perform KM refresh if needed. bool checkSndKMRefresh(int* aw_keyindex); @@ -1350,7 +1249,7 @@ class CUDT SRT_TSA_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 - CLastSend m_LastSend; // Time and stats for the last sending + 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; @@ -1363,7 +1262,7 @@ class CUDT int m_iLightACKCount; // Light ACK counter time_point m_tsNextSendTime; // Scheduled time of next packet sending (normal mode) - CLastSched m_LastSched; // Data for packet scheduling (scheduler mode) + CSendPipeManager m_LastSched; // Data for packet scheduling (scheduler mode) sync::atomic m_iSndLastFullAck; // Last full ACK received SRT_TSA_GUARDED_BY(m_RecvAckLock) @@ -1629,6 +1528,13 @@ class CUDT void removeSndLossUpTo(int32_t seq); bool packData(const SchedPacket& spec, CSndPacket& packet, CNetworkInterface& src_addr); + // XXX + // previous: time when the previous packet was sent over THIS PIPE. + // latest: the latest possible time when the requested packert should be sent + // RETURNS: time when the now requested packet should be scheduled + // : if zero, this request should be cancelled + time_point getNextSendTime(const time_point& previous, const time_point& latest); + bool defineSchedTimes(int32_t lo, int32_t hi, time_point& w_start, duration& w_step); void scheduleRexmitRange(int32_t lo, int32_t hi); diff --git a/srtcore/crypto.cpp b/srtcore/crypto.cpp index 385520a1d..a2593a4a7 100644 --- a/srtcore/crypto.cpp +++ b/srtcore/crypto.cpp @@ -58,7 +58,7 @@ std::string KmStateStr(SRT_KM_STATE state) TAKE(BADCRYPTOMODE); #undef TAKE default: - return hvu::fmtcat("??? (", int(state), ")"); + return hvu::ofcat("??? (", int(state), ")"); } } diff --git a/srtcore/fec.cpp b/srtcore/fec.cpp index 4d15f6edf..9fa155870 100644 --- a/srtcore/fec.cpp +++ b/srtcore/fec.cpp @@ -535,13 +535,13 @@ void FECFilterBuiltin::ClipPacket(Group& g, const CPacket& pkt) ClipData(g, length_net, kflg, timestamp_hw, pkt.data(), pkt.size()); HLOGC(pflog.Debug, log << "FEC DATA PKT CLIP: " - << "FLAGS=" << fmt(kflg, hex) - << " LENGTH[ne]=" << fmt(length_net, hex) - << " TS[he]=" << fmt(timestamp_hw, hex) - << " CLIP STATE: FLAGS=" << fmt(g.flag_clip, hex) - << " LENGTH[ne]=" << fmt(g.length_clip, hex) - << " TS[he]=" << fmt(g.timestamp_clip, hex) - << " PL4=" << fmt(*(uint32_t*)&g.payload_clip[0], hex)); + << "FLAGS=" << fmtm(kflg, hex) + << " LENGTH[ne]=" << fmtm(length_net, hex) + << " TS[he]=" << fmtm(timestamp_hw, hex) + << " CLIP STATE: FLAGS=" << fmtm(g.flag_clip, hex) + << " LENGTH[ne]=" << fmtm(g.length_clip, hex) + << " TS[he]=" << fmtm(g.timestamp_clip, hex) + << " PL4=" << fmtm(*(uint32_t*)&g.payload_clip[0], hex)); } // Clipping a control packet does merely the same, just the packet has @@ -563,13 +563,13 @@ void FECFilterBuiltin::ClipControlPacket(Group& g, const CPacket& pkt) ClipData(g, *length_clip, *flag_clip, timestamp_hw, payload, payload_clip_len); HLOGC(pflog.Debug, log << "FEC/CTL CLIP: " - << "FLAGS=" << fmt(*flag_clip, hex) - << " LENGTH[ne]=" << fmt(*length_clip, hex) - << " TS[he]=" << fmt(timestamp_hw, hex) - << " CLIP STATE: FLAGS=" << fmt(g.flag_clip, hex) - << " LENGTH[ne]=" << fmt(g.length_clip, hex) - << " TS[he]=" << fmt(g.timestamp_clip, hex) - << " PL4=" << fmt(*(uint32_t*)&g.payload_clip[0], hex)); + << "FLAGS=" << fmtm(*flag_clip, hex) + << " LENGTH[ne]=" << fmtm(*length_clip, hex) + << " TS[he]=" << fmtm(timestamp_hw, hex) + << " CLIP STATE: FLAGS=" << fmtm(g.flag_clip, hex) + << " LENGTH[ne]=" << fmtm(g.length_clip, hex) + << " TS[he]=" << fmtm(g.timestamp_clip, hex) + << " PL4=" << fmtm(*(uint32_t*)&g.payload_clip[0], hex)); } void FECFilterBuiltin::ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt) @@ -586,13 +586,13 @@ void FECFilterBuiltin::ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt) ClipData(g, length_net, kflg, timestamp_hw, pkt.buffer, pkt.length); HLOGC(pflog.Debug, log << "FEC REBUILT DATA CLIP: " - << "FLAGS=" << fmt(kflg, hex) - << " LENGTH[ne]=" << fmt(length_net, hex) - << " TS[he]=" << fmt(timestamp_hw, hex) - << " CLIP STATE: FLAGS=" << fmt(g.flag_clip, hex) - << " LENGTH[ne]=" << fmt(g.length_clip, hex) - << " TS[he]=" << fmt(g.timestamp_clip, hex) - << " PL4=" << fmt(*(uint32_t*)&g.payload_clip[0], hex)); + << "FLAGS=" << fmtm(kflg, hex) + << " LENGTH[ne]=" << fmtm(length_net, hex) + << " TS[he]=" << fmtm(timestamp_hw, hex) + << " CLIP STATE: FLAGS=" << fmtm(g.flag_clip, hex) + << " LENGTH[ne]=" << fmtm(g.length_clip, hex) + << " TS[he]=" << fmtm(g.timestamp_clip, hex) + << " PL4=" << fmtm(*(uint32_t*)&g.payload_clip[0], hex)); } void FECFilterBuiltin::ClipData(Group& g, uint16_t length_net, uint8_t kflg, @@ -769,10 +769,10 @@ void FECFilterBuiltin::PackControl(const Group& g, signed char index, SrtPacket& HLOGC(pflog.Debug, log << "FEC: PackControl: hdr(" << (total_size - g.payload_clip.size()) << "): INDEX=" - << int(index) << " LENGTH[ne]=" << fmt(g.length_clip, hex) - << " FLAGS=" << fmt(g.flag_clip, hex) << " TS=" << fmt(g.timestamp_clip, hex) + << int(index) << " LENGTH[ne]=" << fmtm(g.length_clip, hex) + << " FLAGS=" << fmtm(g.flag_clip, hex) << " TS=" << fmtm(g.timestamp_clip, hex) << " PL(" << g.payload_clip.size() << ")[0-4]=" - << fmt(*(uint32_t*)&g.payload_clip[0], hex)); + << fmtm(*(uint32_t*)&g.payload_clip[0], hex)); } diff --git a/srtcore/filelist.maf b/srtcore/filelist.maf index d257cd773..47346c8db 100644 --- a/srtcore/filelist.maf +++ b/srtcore/filelist.maf @@ -13,6 +13,7 @@ crypto.cpp epoll.cpp fec.cpp handshake.cpp +../logging/hvu_compat.cpp list.cpp logger_fas.cpp ../logging/logging.cpp @@ -24,7 +25,6 @@ congctl.cpp schedule_snd.cpp socketconfig.cpp srt_c_api.cpp -../logging/hvu_compat.c strerror_defs.cpp sync.cpp tsbpd_time.cpp diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 0e9e6d186..5a9be759c 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -5469,7 +5469,7 @@ int CUDTGroup::checkLazySpawnTsbPdThread() // Take the last 2 ciphers from the socket ID. string s = fmts(id(), fmtc().fillzero().width(2)); - const string& tn = fmtcat("SRT:GLat:$", s.substr(s.size()-2, 2)); + const string& tn = ofcat("SRT:GLat:$", s.substr(s.size()-2, 2)); ThreadName tnkeep(tn); const string& thname = tn; diff --git a/srtcore/handshake.cpp b/srtcore/handshake.cpp index 4e7578034..cafeaa1f2 100644 --- a/srtcore/handshake.cpp +++ b/srtcore/handshake.cpp @@ -214,13 +214,13 @@ bool CHandShake::valid() string CHandShake::show() { using namespace hvu; - ofmtbufstream so; + ofmt_bufs so; so << "version=" << m_iVersion - << " type=0x" << fmt(m_iType, hex) + << " type=0x" << fmtm(m_iType, hex) << " ISN=" << m_iISN << " MSS=" << m_iMSS << " FLW=" << m_iFlightFlagSize << " reqtype=" << RequestTypeStr(m_iReqType) << " srcID=" << m_iID - << " cookie=" << fmt(m_iCookie, hex) + << " cookie=" << fmtm(m_iCookie, hex) << " srcIP=" << CIPAddress::show(m_piPeerIP); // XXX HS version symbols should be probably declared inside diff --git a/srtcore/packet.h b/srtcore/packet.h index b6c81d0a0..4e4236867 100644 --- a/srtcore/packet.h +++ b/srtcore/packet.h @@ -385,7 +385,7 @@ class CPacket private: // Do not disclose ingredients to the public static const size_t UDP_HDR_SIZE = 8; // 8 bytes of UDP { u16 sport, dport, len, csum }. static const size_t IPv4_HDR_SIZE = 20; // 20 bytes IPv4 - static const size_t IPv6_HDR_SIZE = 32; // 32 bytes IPv6 + static const size_t IPv6_HDR_SIZE = 40; // 40 bytes IPv6 public: static inline size_t udpHeaderSize(int family) { diff --git a/srtcore/packetfilter.cpp b/srtcore/packetfilter.cpp index e80c7b400..fe56141c3 100644 --- a/srtcore/packetfilter.cpp +++ b/srtcore/packetfilter.cpp @@ -224,6 +224,26 @@ size_t PacketFilter::cacheControlPackets(int32_t seq) return m_control.size() - oldsize; } +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; +} + void PacketFilter::copyPacket(SrtPacket& src, int kflg, CPacket& w_packet) { // Now this should be repacked back to CPacket. @@ -250,26 +270,6 @@ void PacketFilter::copyPacket(SrtPacket& src, int kflg, CPacket& w_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); diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 65c9706cd..4cdc04345 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -658,7 +658,7 @@ void CSndQueue::init(CChannel* c) #if HVU_ENABLE_LOGGING ++m_counter; - const string thrname = fmtcat("SRT:SndQ:w", m_counter.load()); + const string thrname = ofcat("SRT:SndQ:w", m_counter.load()); const char* thname = thrname.c_str(); #else const char* thname = "SRT:SndQ"; @@ -818,7 +818,7 @@ void CSndQueue::workerSendOrder() m_SendOrderList.requeue(runner, next_send_time); // [TSA] IDEM IF_HEAVY_LOGGING(sync::steady_clock::time_point now = sync::steady_clock::now()); HLOGC(qslog.Debug, log << "SND updated to " << FormatTime(next_send_time) - << " (now" << fmt(count_microseconds(next_send_time - now), showpos) << "us)"); + << " (now" << fmtm(count_microseconds(next_send_time - now), showpos) << "us)"); } else { @@ -841,10 +841,14 @@ void CSndQueue::workerSendOrder() // to be returned from none(). Here it's returned as empty_list.end(). SocketHolder::socklist_t SocketHolder::empty_list; -SendTask::taskiter_t CMultiplexer::scheduleSend(CUDTSocket* src, int32_t seqno, sched::Type type, const sync::steady_clock::time_point& when) +void CMultiplexer::scheduleSend(CUDTSocket* src, int32_t seqno, sched::Type type, + const sync::steady_clock::time_point& latest_send, + const sync::steady_clock::time_point& latest_delivery) { - SendTask task (SchedPacket(src, seqno, type), when); - return m_SndQueue.m_Scheduler.enqueue_task(src->id(), task); + if (type == sched::TP_REXMIT) + m_SndQueue.m_Scheduler.prescheduleLoss(src, seqno); + else + m_SndQueue.m_Scheduler.prescheduleRegular(SendTaskProto(SchedPacket(src, seqno, type), latest_send, latest_delivery)); } void CSndQueue::workerPacketScheduler() @@ -854,7 +858,6 @@ void CSndQueue::workerPacketScheduler() THREAD_STATE_INIT(thname.c_str()); int interrupt_credit = 5; - for (;;) { if (m_bClosing) @@ -865,16 +868,28 @@ void CSndQueue::workerPacketScheduler() HLOGC(qslog.Debug, log << "SndQ: waiting to get next send candidate..."); THREAD_PAUSED(); - SchedPacket p = m_Scheduler.wait_pop(); + // + // API Scheduling adds directly to the queue only if it is free. + // Otherwise it's added to the "forequeue", with latest sending time. + // If the m_Scheduler contains no packets in the past, instead of + // waiting YET, you pick the earliest (sorted by expected send time) + // packet and enqueue it in the m_Scheduler. At this moment you select + // the sending time basing on the currently shaped sending interval. + + // SENDMODE: PLAIN (0): use default order scheduler. + // SENDMODE: EAGER (1): schedule packets ASAP, just preserve the timestamp + // SENDMODE: PLANNED (2): schedule packets at their declared send time + SchedPacket pspec = m_Scheduler.wait_pop(); THREAD_RESUMED(); INCREMENT_THREAD_ITERATIONS(); - if (p.empty()) + if (pspec.empty()) { --interrupt_credit; if (!m_bClosing) { - LOGC(qslog.Error, log << "SndQ: IPE: scheduler interrupted while queue is running!"); + LOGC(qslog.Error, log << "SndQ: IPE: scheduler interrupted while queue is running (tolerate next " + << interrupt_credit << ")!"); if (!interrupt_credit) { m_bClosing = true; @@ -887,8 +902,8 @@ void CSndQueue::workerPacketScheduler() CSndPacket sndpkt; CNetworkInterface source_addr; - CUDTSocket* s = p.m_Socket.socket; - const bool res = s->core().packData(p, (sndpkt), (source_addr)); + CUDTSocket* s = pspec.m_Socket.socket; + const bool res = s->core().packData(pspec, (sndpkt), (source_addr)); if (!res) { LOGC(qslog.Error, log << "SndQ: IPE: @" << s->id() @@ -904,9 +919,15 @@ void CSndQueue::workerPacketScheduler() const sockaddr_any target_addr = s->core().m_PeerAddr; m_pChannel->sendto(target_addr, sndpkt.pkt, source_addr); + steady_clock::time_point last_send_time = steady_clock::now(); + s->core().m_LastSend.update(last_send_time, sndpkt.pkt.getLength()); + // Restore to maximum after a successful extraction. interrupt_credit = 5; + /* XXX There's no chained scheduling anymore + Any next sending will be based on the direct submission. + steady_clock::time_point when = s->core().calculateRegularSchedTime(); // Schedule the next packet, if possible. @@ -918,6 +939,7 @@ void CSndQueue::workerPacketScheduler() SendTask task (SchedPacket(s, sc.lastSchedSeq() , sched::TP_REGULAR), when); m_Scheduler.enqueue_task(s->id(), task); } + */ } THREAD_EXIT(); } @@ -1238,7 +1260,7 @@ bool CMultiplexer::qualifyToHandleRID(EReadStatus rst, { HLOGC(cnlog.Debug, log << "RID: socket @" << i->m_iID << " still active (remaining " - << fmt(count_microseconds(i->m_tsTTL - tsNow) / 1000000.0, fixed) << "s of TTL)..."); + << fmtm(count_microseconds(i->m_tsTTL - tsNow) / 1000000.0, fixed) << "s of TTL)..."); } const steady_clock::time_point tsLastReq = i->m_pUDT->m_tsLastReqTime; @@ -1472,7 +1494,7 @@ void CRcvQueue::init(int series_size, size_t payload, CChannel* cc) #if HVU_ENABLE_LOGGING const int cnt = ++m_counter; - const string thrname = fmtcat("SRT:RcvQ:w", cnt); + const string thrname = ofcat("SRT:RcvQ:w", cnt); #else const string thrname = "SRT:RcvQ:w"; #endif diff --git a/srtcore/queue.h b/srtcore/queue.h index 5ca79b23a..3bcc3331e 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -1097,7 +1097,9 @@ struct CMultiplexer void removeSender(CUDT* u); // SCHEDULER API - SendTask::taskiter_t scheduleSend(CUDTSocket* src, int32_t seqno, sched::Type type, const sync::steady_clock::time_point& when); + void scheduleSend(CUDTSocket* src, int32_t seqno, sched::Type type, + const sync::steady_clock::time_point& when, + const sync::steady_clock::time_point& delivery); }; diff --git a/srtcore/schedule_snd.cpp b/srtcore/schedule_snd.cpp index 7a2f5d7e3..fb25367f9 100644 --- a/srtcore/schedule_snd.cpp +++ b/srtcore/schedule_snd.cpp @@ -3,29 +3,31 @@ #include #include "schedule_snd.h" +#include "api.h" #include "core.h" #include "logging.h" +#include "ofmt_iostream.h" using namespace std; using namespace srt; using namespace srt::sync; using srt::logging::qslog; +using namespace hvu; namespace srt { -SchedPacket::SchedPacket(CUDTSocket* sock, int32_t seqno, sched::Type t): +SchedPacketInfo::SchedPacketInfo(CUDTSocket* sock, int32_t seqno): m_Socket(CUDT::uglobal()) { if (sock) m_Socket = CUDT::keep(sock); m_iSeqNo = seqno; - m_Type = t; } -void SchedPacket::set_socket(CUDTSocket* sock) +void SchedPacketInfo::set_socket(CUDTSocket* sock) { if (sock) m_Socket = CUDT::keep(sock); @@ -35,17 +37,18 @@ static const char* const schedtype [] = {"regular", "rexmit", "pf-control"}; std::string SendTask::print(SendTask::taskiter_t v) { - std::ostringstream out; + using namespace hvu; + + ofmt_bufs out; // Complicated pre-C++20 time formatting... time_t tval = count_seconds(v->m_tsSendTime.time_since_epoch()); + struct tm tme; + localtime_r(&tval, &tme); int64_t total_usec = count_microseconds(v->m_tsSendTime.time_since_epoch()); int64_t usec = total_usec - (tval * 1000000); - - struct tm tme; - localtime_r(&tval, &tme); - out << "<" << std::put_time(&tme, "%T") << "."; - out << setw(6) << setfill('0') << usec << "> @" << v->m_Packet.m_Socket.id(); + out << "<" << fmt(tme, "%T") << "." << fmt(usec, fmtc().width(6).fillzero()) + << "> @" << v->m_Packet.m_Socket.id(); int32_t seq = v->m_Packet.seqno(); if (seq == SRT_SEQNO_NONE) { @@ -53,16 +56,86 @@ std::string SendTask::print(SendTask::taskiter_t v) } else { - out << " ["; - out << schedtype[v->m_Packet.type()]; - out << "] %" << seq; + out << " [" << schedtype[v->m_Packet.type()] << "] %" << seq; } return out.str(); } std::list SendTask::free_list; -SendTask::taskiter_t SendScheduler::enqueue_task(socket_t id, const SendTask& proto) +SendTask::taskiter_t SendScheduler::createTask(const SendTaskProto& sp) +{ + SRTSOCKET id = sp.m_Packet.m_Socket.socket->id(); + SendTask::tasklist_t& wlist = m_TaskMap[id]; + + wlist.push_back(SendTask (sp)); + SendTask::taskiter_t itask = --wlist.end(); + itask->m_pBaseList = &wlist; + + return itask; +} + +void SendScheduler::prescheduleLoss(CUDTSocket* provider, int32_t first_seqno) +{ + if (m_bBroken) + { + HLOGC(qslog.Debug, log << "Schedule: PRE-ENQ: DENIED, schedule is broken"); + return; + } + + SRTSOCKET id = provider->id(); + sync::ScopedLock lk (m_Lock); + + SchedPacketInfo* psi; + bool isnew; + Tie(psi, isnew) = map_tryinsertp(m_PendingRexmit, id); + + if (!isnew) + return; + + // Insert a new item + psi->set_socket(provider); + psi->m_iSeqNo = first_seqno; + + if (!have_task_ready()) + { + m_TaskReadyCond.notify_all(); + //IF_HEAVY_LOGGING(notif = " [NOTIFIED]"); + } +} + +void SendScheduler::prescheduleRegular(const SendTaskProto& sp) +{ + if (m_bBroken) + { + HLOGC(qslog.Debug, log << "Schedule: PRE-ENQ: DENIED, schedule is broken"); + return; + } + + + sync::ScopedLock lk (m_Lock); + SendTask::taskiter_t itask = createTask(sp); + + m_PendingRegularQueue.push_back(itask); + + IF_HEAVY_LOGGING(sched::Type type = sp.m_Packet.m_Type); + IF_HEAVY_LOGGING(SRTSOCKET id = sp.m_Packet.m_Socket.socket->id()); + IF_HEAVY_LOGGING(const char* notif = ""); + IF_HEAVY_LOGGING(static const char* const type_name[3] = {"REGULAR", "REXMIT", "CONTROL"}); + if (!have_task_ready()) + { + m_TaskReadyCond.notify_all(); + IF_HEAVY_LOGGING(notif = " [NOTIFIED]"); + } + + HLOGC(qslog.Debug, log << "Schedule: PRE-ENQ: @" << id << " %" << sp.m_Packet.m_iSeqNo << " type=" << type_name[type] + << "LATEST: SEND=" << FormatTime(sp.m_tsSendTime) << " DELIVERY=" << FormatTime(sp.m_tsLatestDeliveryTime) << notif); +} + +// NOTE: This is a function that enqueues a task in the queue directly; +// that should be rather not used because you need to know the exact execution time. + /* +SendTask::taskiter_t SendScheduler::enqueue_task(socket_t id, const SchedPacket& proto, const clock_time& when, const clock_time& delivery) { if (m_bBroken) { @@ -71,92 +144,207 @@ SendTask::taskiter_t SendScheduler::enqueue_task(socket_t id, const SendTask& pr } 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; + SendTask::taskiter_t itask = create_task(proto, when, delivery); bool was_ready = have_task_ready(); - // Now enqueue it in m_TaskQueue size_t pos = m_TaskQueue.insert(itask); IF_HEAVY_LOGGING(bool was_first = false); + IF_HEAVY_LOGGING(bool now_ready = false); if (pos == 0) // earliest task { m_tsAboutTime = m_TaskQueue.top()->m_tsSendTime; // INSERTED: will not be empty IF_HEAVY_LOGGING(was_first = true); } + // XXX Shouldn't it update always if m_tsAboutTime was updated? if (!was_ready && have_task_ready()) { - HLOGC(qslog.Debug, log << "Schedule: ENQ: new READY task at T=" << FormatTime(itask->m_tsSendTime) - << (was_first ? " (NEW TOP)" : "") << " - NOTIFY"); m_TaskReadyCond.notify_all(); + IF_HEAVY_LOGGING(now_ready = true); } - else - { - HLOGC(qslog.Debug, log << "Schedule: ENQ: new task at T=" << FormatTime(itask->m_tsSendTime) - << (was_first ? " (NEW TOP)" : "") << (!was_ready ? " (NOW READY)" : " (ONLY ADDED)")); - } + + HLOGC(qslog.Debug, log << "Schedule: ENQ: new" + << fmt_if(now_ready, " READY") + << " task at T=" << FormatTime(itask->m_tsSendTime) + << fmt_if(was_first, " (NEW TOP)") + << fmt_if(was_ready, " (NOW READY)", " (ONLY ADDED)") + << fmt_if(now_ready, " - NOTIFY")); + return itask; } +// */ -bool SendScheduler::have_task_ready() +bool SendScheduler::updatePreschedule(size_t max_sched) { - if (!m_TaskQueue.empty()) + // The next call to wait() will return an already ready task, + // so do not reschedule new ones until they are all dispatched. + if (have_task_ready()) { - SendTask::taskiter_t earliest = m_TaskQueue.top(); - if (earliest->is_ready(clock_type::now())) + HLOGC(qslog.Debug, log << "updatePreschedule: POSTPONED, have ready tasks"); + return true; + } + + if (m_PendingRegularQueue.empty() && m_PendingRexmit.empty()) + { + HLOGC(qslog.Debug, log << "updatePreschedule: NO PENDING tasks"); + return false; + } + + steady_clock::time_point now = steady_clock::now(); + + // Prioritization: + // 1. Check if there is any regular packet already overdue. If so, + // it takes precedence before any retransmission packet. + size_t nsched = 0; + while (!m_PendingRegularQueue.empty()) + { + SendTask::taskiter_t pt = pullTask(m_PendingRegularQueue); + if (pt->m_tsSendTime > now) + break; // stop at first being in the future + + SchedPacket& proto = pt->m_Packet; + proto.m_Socket.socket->core().planSendingTime(proto.m_Type, pt->m_tsLatestDeliveryTime, (pt->m_tsSendTime)); + size_t pos = m_TaskQueue.insert(pt); + + if (pos == 0) // earliest task { - return true; + // INSERTED: will not be empty + m_tsAboutTime = m_TaskQueue.top_raw()->m_tsSendTime; + // NOTE: theoretically you should notify() here, but it's not necessary + // because this function has the same affinity as the wait() caller. + } + + ++nsched; + if (nsched >= max_sched) + { + HLOGC(qslog.Debug, log << "update_forequeue: enqueued " << max_sched << " REGULAR tasks, " + << fmt_if(pos == 0, " NEW HEAD,") << " still pending: " + << m_PendingRegularQueue.size() << " REG + " << m_PendingRegularQueue.size() << " REXMIT"); + return have_task_ready(); } } - return false; -} -bool SendScheduler::wait() -{ - UniqueLock lk (m_Lock); - return wait_extlock((lk)); -} + // 2. Review the retransmission packets; insert those that still have + // remaining delivery time. Drop the others. -bool SendScheduler::wait_extlock(UniqueLock& lk) -{ - IF_HEAVY_LOGGING(typedef steady_clock::time_point ClockTime); - for (;;) + IF_HEAVY_LOGGING(int regsched = nsched); + IF_HEAVY_LOGGING(bool new_head = false); + std::vector to_delete; + + // Ok, the rexmit queue is different. + // We have normally information about ONE loss, which at the moment of + // pickup need not hold. We then run the loop by index, with notifying up + // to which element we have passed the task to the task queue, and those + // will be then removed. + + // Walk through all entries. Later delete those that don't report any more lost packets + for (map::const_iterator i = m_PendingRexmit.begin(); i != m_PendingRexmit.end(); ++i) { - if (m_bBroken) + SendTaskProto proto; + + for (;;) { - HLOGC(qslog.Debug, log << "Schedule: WAIT: not waiting, schedule is broken"); - return false; + // This should check what losses are expected to be sent, which losses + // are too old and should be deleted, and whether anything is still about to be sent. + int loss_chain = i->second.m_Socket.socket->core().extractPlannedLoss((proto)); + // RETURNS: + // 0: No loss scheduled at the moment for that iteration (note: should not happen IN ITERATION) + // 1: One loss was detected, but then there is none. + // 2: The loss was extracted and there's a new one waiting. + if (loss_chain) + { + ++nsched; + // We have something - schedule it. + SendTask::taskiter_t itask = createTask(proto); + + size_t pos = m_TaskQueue.insert(itask); + if (pos == 0) // earliest task + { + m_tsAboutTime = m_TaskQueue.top_raw()->m_tsSendTime; + IF_HEAVY_LOGGING(new_head = true); + } + } + + if (loss_chain != 2) + { + to_delete.push_back(i->first); + break; // forget about this one; check the next socket + } + + if (nsched >= max_sched) + { + HLOGC(qslog.Debug, log << "update_forequeue: enqueued " << max_sched << " = " << regsched + << " REG + " << (nsched - regsched) << " REXMIT tasks," + << fmt_if(new_head, " NEW HEAD,") << " still pending: " + << m_PendingRegularQueue.size() << " REG + " << m_PendingRexmit.size() << " sockets in REXMIT"); + return have_task_ready(); + } } - IF_HEAVY_LOGGING(ClockTime now = steady_clock::now()); - if (have_task_ready()) + if (nsched >= max_sched) { - IF_HEAVY_LOGGING(ClockTime next = m_TaskQueue.top()->m_tsSendTime); - HLOGC(qslog.Debug, log << "Schedule: WAIT: task ready since " << FormatDurationAuto(now - next)); - break; + return have_task_ready(); } + } + + // Delete entries for sockets that do not report any rexmit + for (size_t i = 0; i < to_delete.size(); ++i) + m_PendingRexmit.erase(to_delete[i]); + + IF_HEAVY_LOGGING(int rexsched = nsched); + // 3. Try to schedule regular packets, if there are still free slots + while (!m_PendingRegularQueue.empty()) + { + SendTask::taskiter_t pt = pullTask(m_PendingRegularQueue); + SchedPacket& proto = pt->m_Packet; + proto.m_Socket.socket->core().planSendingTime(proto.m_Type, pt->m_tsLatestDeliveryTime, (pt->m_tsSendTime)); + size_t pos = m_TaskQueue.insert(pt); -#if ENABLE_HEAVY_LOGGING - if (m_TaskQueue.empty()) + if (pos == 0) // earliest task { - LOGC(qslog.Debug, log << "Schedule: WAIT: task NOT ready, NO NEW TASKS, WAIT FOR SIGNAL"); + // INSERTED: will not be empty + m_tsAboutTime = m_TaskQueue.top_raw()->m_tsSendTime; + // NOTE: theoretically you should notify() here, but it's not necessary + // because this function has the same affinity as the wait() caller. + IF_HEAVY_LOGGING(new_head = true); } - else + + ++nsched; + if (nsched >= max_sched) { - 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"); + break; } -#endif - - m_TaskReadyCond.wait(lk); } - return true; + HLOGC(qslog.Debug, log << "update_forequeue: enqueued " << nsched << " = " + << regsched << "YD + " << (rexsched - regsched) << "RX + " + << (nsched - rexsched) << "RG tasks," + << fmt_if(new_head, " NEW HEAD,") << " still pending: " + << m_PendingRegularQueue.size() << " REG + " << m_PendingRexmit.size() << " sockets in REXMIT"); + return have_task_ready(); +} + +bool SendScheduler::have_task_ready() +{ + // XXX Make sure it works. The m_tsAboutTime is being updated + // with every update in m_Scheduler, so it should keep the top + // element's time, or be 0 if there's no top element. + + return !sync::is_zero(m_tsAboutTime) && m_tsAboutTime <= clock_type::now(); +#if 0 + + if (!m_TaskQueue.empty()) + { + SendTask::taskiter_t earliest = m_TaskQueue.top(); + if (earliest->is_ready(clock_type::now())) + { + return true; + } + } + return false; +#endif } void SendScheduler::withdraw(socket_t id) @@ -185,7 +373,7 @@ void SendScheduler::withdraw(socket_t id) pop_update_time(); #if HVU_ENABLE_HEAVY_LOGGING - hvu::ofmtbufstream nextone; + hvu::ofmt_bufs nextone; if (m_TaskQueue.empty()) nextone << "NO NEXT TASK"; else @@ -236,22 +424,49 @@ SchedPacket SendScheduler::wait_pop() // Wait until the time has come to execute // the next task. Extract the task structure // and remove the task from the list. + for (;;) { if (m_bBroken) { HLOGC(qslog.Debug, log << "Schedule: wait_pop: broken"); - break; + return SchedPacket(); } - bool have = wait_extlock((lk)); - if (have) + + // This will move pending tasks if there's no ready task yet. + // Returns true if there's at least one task ready for pickup + if (updatePreschedule(4 /*XXX USE CONSTANT*/)) { + HLOGC(qslog.Debug, log << "Schedule: WAIT: task ready since " << FormatDurationAuto(steady_clock::now() - m_tsAboutTime)); break; } + + if (m_TaskQueue.empty()) // This means that also m_ForeQueue was empty + { + HLOGC(qslog.Debug, log << "Schedule: WAIT: task NOT ready, NO NEW TASKS, WAIT FOR SIGNAL)"); + // This will be signaled if anything is added to m_ForeQueue or m_TaskQueue. + m_TaskReadyCond.wait(lk); + } else { - HLOGC(qslog.Debug, log << "Schedule: wait_pop: SPURIOUS"); + clock_time now = steady_clock::now(); + HLOGC(qslog.Debug, log << "Schedule: WAIT: task NOT ready, next in " << FormatDurationAuto(m_tsAboutTime - now)); + + duration remain = m_tsAboutTime - now; + if (remain > duration()) // m_tsAboutTime is in the future + { + // Sleep accuracy may be problematic. Keep the rule of 10ms as the + // minimum "long time"; if this is still so much time to wait, sleep + // for 3/4 of this time and then check again. If less time remained, + // sleep always 1ms. Long time sleeps should not happen during the + // transmission. + if (remain > sync::milliseconds_from(10)) + this_thread::sleep_for(remain * 3 / 4); + else + this_thread::sleep_for(milliseconds_from(1)); + } } + // CONTINUE. } // Here we are sure that the top() task is ready to execute SendTask::taskiter_t itask = m_TaskQueue.pop(); @@ -271,7 +486,9 @@ SchedPacket SendScheduler::wait_pop() 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()]); + 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 7130d8bf3..2e6cb0fea 100644 --- a/srtcore/schedule_snd.h +++ b/srtcore/schedule_snd.h @@ -33,17 +33,12 @@ namespace sched // - - If packetfilter control packet is ready AFTER that, schedule that, too. // - When dispatching LOSSREPORT: schedule rexmit packet // - When NAKREPORT timer expired: schedule rexmit packet -struct SchedPacket + +struct SchedPacketInfo { SocketKeeper m_Socket; int32_t m_iSeqNo; - sched::Type m_Type; - // NOTE: Both constructor and set_socket() call will need to - // perform the official acquisition of the socket, which requires - // locking CUDTUnited::m_GlobControlLock. Further copying of the - // SocketKeeper object doesn't require any locking. - SchedPacket(CUDTSocket* sock = NULL, int32_t seqno = SRT_SEQNO_NONE, sched::Type t = sched::TP_REGULAR); void set_socket(CUDTSocket* sock); SRTSOCKET id() const { return m_Socket.id(); } @@ -51,17 +46,61 @@ struct SchedPacket bool empty() const { return m_iSeqNo == SRT_SEQNO_NONE; } int32_t seqno() const { return m_iSeqNo; } + + SchedPacketInfo(CUDTSocket* sock = NULL, int32_t seqno = SRT_SEQNO_NONE); +}; + +struct SchedPacket: SchedPacketInfo +{ + sched::Type m_Type; + + // NOTE: Both constructor and set_socket() call will need to + // perform the official acquisition of the socket, which requires + // locking CUDTUnited::m_GlobControlLock. Further copying of the + // SocketKeeper object doesn't require any locking. + SchedPacket(CUDTSocket* sock = NULL, int32_t seqno = SRT_SEQNO_NONE, sched::Type t = sched::TP_REGULAR): + SchedPacketInfo(sock, seqno), + m_Type(t) + { + } sched::Type type() const { return m_Type; } }; -struct SendTask +struct SendTaskProto +{ + typedef sync::steady_clock::time_point ClockTime; + // DOUBLE INTERPRETATION: + // - for pending queue, it's the latest scheduling time = mctrl.srctime + // - for schedule queue, it's the time to pickup + ClockTime m_tsSendTime; + ClockTime m_tsLatestDeliveryTime; + SchedPacket m_Packet; + + SendTaskProto() + : m_tsSendTime(), m_Packet() {} + + SendTaskProto(const SchedPacket& sp, sync::steady_clock::time_point when, sync::steady_clock::time_point delivery) + : m_tsSendTime(when), m_tsLatestDeliveryTime(delivery), m_Packet(sp) {} +}; + +struct SendTask: SendTaskProto { + // NODE FIELDS for HeapSet typedef std::list tasklist_t; typedef typename tasklist_t::iterator taskiter_t; typedef sync::steady_clock::time_point key_type; - key_type m_tsSendTime; - SchedPacket m_Packet; + + key_type key() const { return m_tsSendTime; } sync::atomic m_zHeapPos; // Required by HeapSet + + // "Payload" fields. + + // = mctrl.srctime + optimisticRTT() + latency + // This is the latest time when the packet is expected to be + // given up to the application - at latest, if in EAGER mode. + // REXMIT packets that are past this time already should be dropped. + key_type m_tsLatestDeliveryTime; + SchedPacket m_Packet; std::list* m_pBaseList; // Same definition as by HeapSet; here a shortcut. @@ -70,21 +109,25 @@ struct SendTask static const size_t npos = std::string::npos; SendTask() - : m_tsSendTime(), m_Packet(), m_zHeapPos(npos), m_pBaseList(0) {} + : SendTaskProto(), m_zHeapPos(npos), m_pBaseList(0) {} - SendTask(const SchedPacket& sp, sync::steady_clock::time_point when) - : m_tsSendTime(when), m_Packet(sp), m_zHeapPos(npos), m_pBaseList(0) {} + SendTask(const SendTaskProto& proto) + : SendTaskProto(proto), m_zHeapPos(npos), m_pBaseList(0) {} // Note: Copying a task is only allowed because of the need // to move from one container to another. A single task that is // pinned to a sender buffer may however exist only in one instance. SendTask(const SendTask& src): - m_tsSendTime(src.m_tsSendTime), + SendTaskProto(src.m_Packet, src.m_tsSendTime, src.m_tsLatestDeliveryTime), + // m_zHeapPos(src.m_zHeapPos.load()), + m_zHeapPos(npos), // Copied element is NEVER in ths container! m_Packet(src.m_Packet), - m_zHeapPos(src.m_zHeapPos.load()), m_pBaseList(src.m_pBaseList) {} + // convenience + void set_time(sync::steady_clock::time_point tp) { m_tsSendTime = tp; } + bool is_ready(key_type basetime) const { return m_tsSendTime < basetime; @@ -116,17 +159,45 @@ struct SendScheduler typedef SRTSOCKET socket_t; typedef sync::steady_clock clock_type; typedef clock_type::time_point clock_time; + typedef clock_type::duration duration; protected: std::map m_TaskMap; HeapSet m_TaskQueue; + typedef std::deque pending_t; + pending_t m_PendingRegularQueue; + + // We use map in order to keep always only one entry per socket. + std::map m_PendingRexmit; clock_time m_tsAboutTime; - void pop_update_time(); sync::Mutex m_Lock; sync::Condition m_TaskReadyCond; sync::atomic m_bBroken; + SRT_TSA_NEEDS_LOCKED(m_Lock) + void pop_update_time(); + + // Helper functions to operate with the pending queues + // You better check if q.empty() before the call! + SendTask::taskiter_t pullTask(pending_t& q) + { + SendTask::taskiter_t i = *q.begin(); + q.pop_front(); + return i; + } + + void deletePending(pending_t& q) + { + for (pending_t::iterator i = q.begin(); i != q.end(); ++i) + { + SendTask::taskiter_t pt = *i; + pt->m_pBaseList->erase(pt); + // NOTE: All elements become dangling iterators here! + } + q.clear(); + } + public: const HeapSet& queue() { return m_TaskQueue; } @@ -141,17 +212,24 @@ struct SendScheduler return !m_bBroken; } - SendTask::taskiter_t enqueue_task(socket_t id, const SendTask& proto); + // SendTask::taskiter_t enqueue_task(socket_t id, const SendTask& proto); + void prescheduleRegular(const SendTaskProto& sp); + void prescheduleLoss(CUDTSocket* provider, int32_t first_seqno); - void update_task(SendTask::taskiter_t ti); + SendTask::taskiter_t createTask(const SendTaskProto& sp); + void updateTask(SendTask::taskiter_t ti); protected: // This is NOLOCK; derived classes please use lock. + SRT_TSA_NEEDS_LOCKED(m_Lock) bool have_task_ready(); + SRT_TSA_NEEDS_LOCKED(m_Lock) + bool updatePreschedule(size_t max_sched); + public: + // Wait until the time has come - bool wait(); bool wait_extlock(srt::sync::UniqueLock&); void withdraw(socket_t id); diff --git a/srtcore/socketconfig.cpp b/srtcore/socketconfig.cpp index 4b0361d3f..0f3298327 100644 --- a/srtcore/socketconfig.cpp +++ b/srtcore/socketconfig.cpp @@ -759,8 +759,8 @@ struct CSrtConfigSetter { co.uKmPreAnnouncePkt = (km_refresh - 1) / 2; LOGC(aclog.Warn, - log << "SRTO_KMREFRESHRATE=0x" << fmt(km_refresh, std::hex) << ": setting SRTO_KMPREANNOUNCE=0x" - << fmt(co.uKmPreAnnouncePkt, std::hex)); + log << "SRTO_KMREFRESHRATE=0x" << fmtm(km_refresh, std::hex) << ": setting SRTO_KMPREANNOUNCE=0x" + << fmtm(co.uKmPreAnnouncePkt, std::hex)); } } }; @@ -783,8 +783,8 @@ struct CSrtConfigSetter if (km_preanno > (kmref - 1) / 2) { LOGC(aclog.Error, - log << "SRTO_KMPREANNOUNCE=0x" << fmt(km_preanno, std::hex) - << " exceeds KmRefresh/2, 0x" << fmt((kmref - 1) / 2, std::hex) + log << "SRTO_KMPREANNOUNCE=0x" << fmtm(km_preanno, std::hex) + << " exceeds KmRefresh/2, 0x" << fmtm((kmref - 1) / 2, std::hex) << " - OPTION REJECTED."); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } @@ -959,9 +959,9 @@ struct CSrtConfigSetter { const int val = cast_optval(optval, optlen); - if (val < 0 || val > 1) + if (val < 0 || val > 2) { - LOGC(aclog.Error, log << "OPTION: sendmode: only 0 and 1 allowed"); + LOGC(aclog.Error, log << "OPTION: sendmode: allowed 0=live 1=stow 2=planned"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } diff --git a/srtcore/srt.h b/srtcore/srt.h index 799231a50..b36c11ada 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -310,6 +310,13 @@ typedef enum SRT_TRANSTYPE SRTT_INVALID } SRT_TRANSTYPE; +typedef enum SRT_SENDMODETYPE +{ + SRTM_LIVE = 0, // default + SRTM_EAGER = 1, + SRTM_PLANNED = 2 +} SRT_SENDMODETYPE; + // These sizes should be used for Live mode. In Live mode you should not // exceed the size that fits in a single MTU. @@ -326,7 +333,7 @@ SRT_ATR_DEPRECATED_PX static const int SRT_LIVE_MAX_PLSIZE SRT_ATR_DEPRECATED = // * without FEC packet filter (see SRTO_PACKETFILTER) // * without AEAD through AES-GCM (see SRTO_CRYPTOMODE) static const int SRT_MAX_PLSIZE_AF_INET = 1456; // MTU(1500) - IPv4.hdr(20) - UDP.hdr(8) - SRT.hdr(16) -static const int SRT_MAX_PLSIZE_AF_INET6 = 1444; // MTU(1500) - IPv6.hdr(32) - UDP.hdr(8) - SRT.hdr(16) +static const int SRT_MAX_PLSIZE_AF_INET6 = 1436; // MTU(1500) - IPv6.hdr(40) - UDP.hdr(8) - SRT.hdr(16) // Latency for Live transmission: default is 120 static const int SRT_LIVE_DEF_LATENCY_MS = 120; diff --git a/srtcore/srt_c_api.cpp b/srtcore/srt_c_api.cpp index 90aab02f2..dcc533981 100644 --- a/srtcore/srt_c_api.cpp +++ b/srtcore/srt_c_api.cpp @@ -20,6 +20,7 @@ written by #include #include #include "srt.h" +#include "version.h" // GENERATED! #include "access_control.h" #include "common.h" #include "packet.h" diff --git a/srtcore/sync.cpp b/srtcore/sync.cpp index bbf1754df..fd4bbf215 100644 --- a/srtcore/sync.cpp +++ b/srtcore/sync.cpp @@ -66,21 +66,60 @@ std::string FormatTime(const steady_clock::time_point& timestamp) const uint64_t minutes = total_sec / 60 - (days * 24 * 60) - hours * 60; const uint64_t seconds = total_sec - (days * 24 * 60 * 60) - hours * 60 * 60 - minutes * 60; steady_clock::time_point frac = timestamp - seconds_from(total_sec); - ofmtbufstream out; + ofmt_bufs out; if (days) - out << days << OFMT_RAWSTR("D "); + out << days << OFMT_SV("D "); fmtc d02 = fmtc().dec().fillzero().width(2), dec0 = fmtc().dec().fillzero().width(decimals); - out << fmt(hours, d02) << OFMT_RAWSTR(":") - << fmt(minutes, d02) << OFMT_RAWSTR(":") - << fmt(seconds, d02) << OFMT_RAWSTR(".") + out << fmt(hours, d02) << OFMT_SV(":") + << fmt(minutes, d02) << OFMT_SV(":") + << fmt(seconds, d02) << OFMT_SV(".") << fmt(frac.time_since_epoch().count(), dec0) - << OFMT_RAWSTR(" [STDY]"); + << OFMT_SV(" [STDY]"); return out.str(); } +std::string FormatTime2(const steady_clock::time_point& timestamp) +{ + using namespace hvu; + if ((timestamp) == steady_clock::time_point()) + { + // Use special string for 0 + return "00:00:00.000000 [STDY]"; + } + + static const int decimals = clockSubsecondPrecision(); + const uint64_t total_sec = count_seconds(timestamp.time_since_epoch()); + int64_t days, hours, minutes, seconds; + static const int64_t + SEC_DAY = 60 * 60 * 24, + SEC_HOUR = 60 * 60, + SEC_MIN = 60; + + int64_t runsec = total_sec; + Tie(days, runsec) = divmod(runsec, SEC_DAY); + Tie(hours, runsec) = divmod(runsec, SEC_HOUR); + Tie(minutes, runsec) = divmod(runsec, SEC_MIN); + seconds = runsec; + steady_clock::time_point frac = timestamp - seconds_from(total_sec); + ofmt_bufs out; + if (days) + out << days << OFMT_SV("D "); + + fmtc d02 = fmtc().dec().fillzero().width(2), + dec0 = fmtc().dec().fillzero().width(decimals); + + out << fmt(hours, d02) << OFMT_SV(":") + << fmt(minutes, d02) << OFMT_SV(":") + << fmt(seconds, d02) << OFMT_SV(".") + << fmt(frac.time_since_epoch().count(), dec0) + << OFMT_SV(" [STDY]"); + return out.str(); +} + + std::string FormatTimeSys(const steady_clock::time_point& timestamp) { using namespace hvu; @@ -91,31 +130,31 @@ std::string FormatTimeSys(const steady_clock::time_point& timestamp) const int64_t delta_s = static_cast(floor((static_cast(count_microseconds(now_timestamp.time_since_epoch()) % 1000000) + delta_us) / 1000000.0)); const time_t tt = now_s + delta_s; - struct tm tm = SysLocalTime(tt); // in seconds + struct tm tm = sys_localtime(tt); // in seconds char tmp_buf[512]; size_t tmp_size = strftime(tmp_buf, 512, "%X.", &tm); // Mind the theoretically possible error case if (!tmp_size) return "