From 463085c0fcffa6565b569cdb0055b7cb8afb0ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 29 Jul 2022 12:44:50 +0200 Subject: [PATCH 01/62] First trial version --- srtcore/buffer.cpp | 17 +- srtcore/buffer.h | 14 +- srtcore/core.cpp | 49 +++-- srtcore/core.h | 19 +- srtcore/group.cpp | 472 +++++++++++++++++++++++++++++++++++++--- srtcore/group.h | 56 ++++- srtcore/queue.cpp | 85 ++++---- srtcore/queue.h | 3 + srtcore/srt.h | 1 + srtcore/srt_attr_defs.h | 9 + srtcore/utilities.h | 10 +- 11 files changed, 621 insertions(+), 114 deletions(-) diff --git a/srtcore/buffer.cpp b/srtcore/buffer.cpp index 635d21e90..716c883bb 100644 --- a/srtcore/buffer.cpp +++ b/srtcore/buffer.cpp @@ -228,7 +228,7 @@ CSndBuffer::~CSndBuffer() releaseMutex(m_BufLock); } -void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) +void CSndBuffer::addBuffer(const char* data, int len, void* selink, SRT_MSGCTRL& w_mctrl) { int32_t& w_msgno = w_mctrl.msgno; int32_t& w_seqno = w_mctrl.pktseq; @@ -308,10 +308,11 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) // [PB_FIRST] [PB_LAST] - 2 packets per message // [PB_SOLO] - 1 packet per message + s->m_pSelectedLink = selink; 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; @@ -405,7 +406,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::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc, void* member_marker) { int readlen = 0; w_seqnoinc = 0; @@ -456,6 +457,16 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, continue; } + if (member_marker && member_marker != m_pCurrBlock->m_pSelectedLink) + { + LOGC(bslog.Debug, log << CONID() << "CSndBuffer: skipping packet %" << p->m_iSeqNo << " #" << p->getMsgSeq() + << " intendef for link " << m_pCurrBlock->m_pSelectedLink); + // Skip this packet due to not being set up to be sent over THIS link in balancing mode. + readlen = 0; + ++w_seqnoinc; + continue; + } + HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: extracting packet size=" << readlen << " to send"); break; } diff --git a/srtcore/buffer.h b/srtcore/buffer.h index 3ac7b3e95..de59df768 100644 --- a/srtcore/buffer.h +++ b/srtcore/buffer.h @@ -175,7 +175,7 @@ class CSndBuffer /// @param [in] len size of the block. /// @param [inout] w_mctrl Message control data SRT_ATTR_EXCLUDES(m_BufLock) - void addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl); + void addBuffer(const char* data, int len, void* link, SRT_MSGCTRL& w_mctrl); /// Read a block of data from file and insert it into the sending list. /// @param [in] ifs input file stream. @@ -191,7 +191,7 @@ class CSndBuffer /// @param [out] seqnoinc the number of packets skipped due to TTL, so that seqno should be incremented. /// @return Actual length of data read. SRT_ATTR_EXCLUDES(m_BufLock) - int readData(CPacket& w_packet, time_point& w_origintime, int kflgs, int& w_seqnoinc); + int readData(CPacket& w_packet, time_point& w_origintime, int kflgs, int& w_seqnoinc, void* member_marker); /// Peek an information on the next original data packet to send. /// @return origin time stamp of the next packet; epoch start time otherwise. @@ -264,8 +264,18 @@ class CSndBuffer time_point m_tsRexmitTime; // packet retransmission time int m_iTTL; // time to live (milliseconds) + void* m_pSelectedLink; + Block* m_pNext; // next block + // This is only to assign fields that will be different + // only in specific situations, so normally these fields + // will not be overwritten. + Block() + : m_pSelectedLink(NULL) + { + } + int32_t getMsgSeq() { // NOTE: this extracts message ID with regard to REXMIT flag. diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 15a36a219..490cb5d22 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -949,9 +949,11 @@ void srt::CUDT::open() m_tsLastRspAckTime = currtime; m_tsLastSndTime.store(currtime); +#if ENABLE_BONDING m_tsUnstableSince = steady_clock::time_point(); m_tsFreshActivation = steady_clock::time_point(); m_tsWarySince = steady_clock::time_point(); +#endif m_iReXmitCount = 1; m_iPktCount = 0; @@ -6473,13 +6475,18 @@ int srt::CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, int64 mctrl.msgttl = msttl; mctrl.inorder = inorder; mctrl.srctime = srctime; - return this->sendmsg2(data, len, (mctrl)); + return this->sendMessageInternal(data, len, NULL, (mctrl)); +} + +int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) +{ + return sendMessageInternal(data, len, NULL, (w_mctrl)); } // [[using maybe_locked(CUDTGroup::m_GroupLock, m_parent->m_GroupOf != NULL)]] // GroupLock is applied when this function is called from inside CUDTGroup::send, // which is the only case when the m_parent->m_GroupOf is not NULL. -int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) +int srt::CUDT::sendMessageInternal(const char *data, int len, void* selink, SRT_MSGCTRL& w_mctrl) { // throw an exception if not connected if (m_bBroken || m_bClosing) @@ -6733,7 +6740,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) // - OUTPUT: value of the sequence number to be put on the first packet at the next sendmsg2 call. // We need to supply to the output the value that was STAMPED ON THE PACKET, // which is seqno. In the output we'll get the next sequence number. - m_pSndBuffer->addBuffer(data, size, (w_mctrl)); + m_pSndBuffer->addBuffer(data, size, selink, (w_mctrl)); m_iSndNextSeqNo = w_mctrl.pktseq; w_mctrl.pktseq = seqno; @@ -9414,7 +9421,7 @@ bool srt::CUDT::isRetransmissionAllowed(const time_point& tnow SRT_ATR_UNUSED) return true; } -std::pair srt::CUDT::packData(CPacket& w_packet) +bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime) { int payload = 0; bool probe = false; @@ -9424,6 +9431,8 @@ std::pair srt::CUDT::packData(CPacket& w_packet) const steady_clock::time_point enter_time = steady_clock::now(); + w_nexttime = enter_time; + if (!is_zero(m_tsNextSendTime) && enter_time > m_tsNextSendTime) { m_tdSendTimeDiff = m_tdSendTimeDiff.load() + (enter_time - m_tsNextSendTime); @@ -9440,7 +9449,7 @@ std::pair srt::CUDT::packData(CPacket& w_packet) // start the dissolving process, this process will // not be started until this function is finished. if (!m_bOpened) - return std::make_pair(false, enter_time); + return false; payload = isRetransmissionAllowed(enter_time) ? packLostData((w_packet), (origintime)) @@ -9464,11 +9473,11 @@ std::pair srt::CUDT::packData(CPacket& w_packet) } else { - if (!packUniqueData(w_packet, origintime)) + if (!packUniqueData((w_packet), (origintime))) { m_tsNextSendTime = steady_clock::time_point(); - m_tdSendTimeDiff = steady_clock::duration(); - return std::make_pair(false, enter_time); + m_tdSendTimeDiff = steady_clock::duration::zero(); + return false; } new_packet_packed = true; @@ -9499,11 +9508,11 @@ std::pair srt::CUDT::packData(CPacket& w_packet) */ if (origintime >= m_stats.tsStartTime) { - setPacketTS(w_packet, origintime); + setPacketTS((w_packet), origintime); } else { - setPacketTS(w_packet, steady_clock::now()); + setPacketTS((w_packet), steady_clock::now()); LOGC(qslog.Warn, log << "packData: reference time=" << FormatTime(origintime) << " is in the past towards start time=" << FormatTime(m_stats.tsStartTime) << " - setting NOW as reference time for the data packet"); @@ -9511,7 +9520,7 @@ std::pair srt::CUDT::packData(CPacket& w_packet) } else { - setPacketTS(w_packet, steady_clock::now()); + setPacketTS((w_packet), steady_clock::now()); } } @@ -9585,7 +9594,9 @@ std::pair srt::CUDT::packData(CPacket& w_packet) #endif } - return std::make_pair(payload >= 0, m_tsNextSendTime); + w_nexttime = m_tsNextSendTime; + + return payload >= 0; } bool srt::CUDT::packUniqueData(CPacket& w_packet, time_point& w_origintime) @@ -9607,7 +9618,17 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet, time_point& w_origintime) // isn't a useless redundant state copy. If it is, then taking the flags here can be removed. const int kflg = m_pCryptoControl->getSndCryptoFlags(); int pktskipseqno = 0; - const int pld_size = m_pSndBuffer->readData((w_packet), (w_origintime), kflg, (pktskipseqno)); + void* link_marker = 0; +#if ENABLE_BONDING + if (m_parent->m_GroupOf) + { + // This marker is required for a decision whether the packet + // stored in the buffer is intended to be sent over this very link. + // Otherwise it will be skipped. + link_marker = m_parent->m_GroupMemberData; + } +#endif + const int pld_size = m_pSndBuffer->readData((w_packet), (w_origintime), kflg, (pktskipseqno), link_marker); if (pktskipseqno) { // Some packets were skipped due to TTL expiry. @@ -10017,7 +10038,7 @@ int srt::CUDT::processData(CUnit* in_unit) // This check is needed as after getting the lock the socket // could be potentially removed. It is however granted that as long // as gi is non-NULL iterator, the group does exist and it does contain - // this socket as member (that is, 'gi' cannot be a dangling iterator). + // this socket as member (that is, 'gi' cannot be a dangling pointer). if (gi != NULL) { if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely diff --git a/srtcore/core.h b/srtcore/core.h index 2bd606bee..2188cf2a2 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -313,8 +313,10 @@ class CUDT int32_t schedSeqNo() const { return m_iSndNextSeqNo; } bool overrideSndSeqNo(int32_t seq); +#if ENABLE_BONDING sync::steady_clock::time_point lastRspTime() const { return m_tsLastRspTime.load(); } sync::steady_clock::time_point freshActivationStart() const { return m_tsFreshActivation; } +#endif int32_t rcvSeqNo() const { return m_iRcvCurrSeqNo; } int flowWindowSize() const { return m_iFlowWindowSize; } @@ -605,6 +607,8 @@ class CUDT SRT_ATR_NODISCARD int sendmsg2(const char* data, int len, SRT_MSGCTRL& w_m); + SRT_ATR_NODISCARD int sendMessageInternal(const char* data, int len, void* selink, SRT_MSGCTRL& w_m); + SRT_ATR_NODISCARD int recvmsg(char* data, int len, int64_t& srctime); SRT_ATR_NODISCARD int recvmsg2(char* data, int len, SRT_MSGCTRL& w_m); SRT_ATR_NODISCARD int receiveMessage(char* data, int len, SRT_MSGCTRL& w_m, int erh = 1 /*throw exception*/); @@ -1055,12 +1059,11 @@ class CUDT /// Pack in CPacket the next data to be send. /// /// @param packet [in, out] a CPacket structure to fill + /// @param nexttime [out] Time when this socket should be next time picked up for processing. /// - /// @return A pair of values is returned (is_payload_valid, timestamp). - /// If is_payload_valid is false, there was nothing packed for sending, - /// and the timestamp value should be ignored. - /// The timestamp is the full source/origin timestamp of the data. - std::pair packData(CPacket& packet); + /// @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); int processData(CUnit* unit); void processClose(); @@ -1081,7 +1084,7 @@ class CUDT /// @param seq first unacknowledged packet sequence number. void ackDataUpTo(int32_t seq); -#if ENABLE_BONDING && ENABLE_NEW_RCVBUFFER +#if ENABLE_NEW_BONDING /// @brief Drop packets in the recv buffer behind group_recv_base. /// Updates m_iRcvLastSkipAck if it's behind group_recv_base. void dropToGroupRecvBase(); @@ -1121,10 +1124,12 @@ class CUDT static const int PACKETPAIR_MASK = 0xF; private: // Timers functions +#if ENABLE_BONDING time_point m_tsFreshActivation; // GROUPS: time of fresh activation of the link, or 0 if past the activation phase or idle time_point m_tsUnstableSince; // GROUPS: time since unexpected ACK delay experienced, or 0 if link seems healthy time_point m_tsWarySince; // GROUPS: time since an unstable link has first some response - +#endif + static const int BECAUSE_NO_REASON = 0, // NO BITS BECAUSE_ACK = 1 << 0, BECAUSE_LITEACK = 1 << 1, diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 4847efe63..c60011bfe 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -277,6 +277,10 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) , m_bClosing(false) , m_iLastSchedSeqNo(SRT_SEQNO_NONE) , m_iLastSchedMsgNo(SRT_MSGNO_NONE) +#if ENABLE_NEW_RCVBUFFER + , m_uBalancingRoll(0) + , m_RandomCredit(16) +#endif { setupMutex(m_GroupLock, "Group"); setupMutex(m_RcvDataLock, "RcvData"); @@ -289,6 +293,8 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) // Set this data immediately during creation before // two or more sockets start arguing about it. m_iLastSchedSeqNo = CUDT::generateISN(); + + m_cbSelectLink.set(this, &CUDTGroup::linkSelect_plain_fw); } CUDTGroup::~CUDTGroup() @@ -1022,11 +1028,10 @@ int CUDTGroup::send(const char* buf, int len, SRT_MSGCTRL& w_mc) case SRT_GTYPE_BACKUP: return sendBackup(buf, len, (w_mc)); - /* to be implemented - case SRT_GTYPE_BALANCING: return sendBalancing(buf, len, (w_mc)); + /* to be implemented case SRT_GTYPE_MULTICAST: return sendMulticast(buf, len, (w_mc)); */ @@ -1034,6 +1039,16 @@ int CUDTGroup::send(const char* buf, int len, SRT_MSGCTRL& w_mc) } int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) +{ + return sendSelectable(buf, len, (w_mc), false); +} + +int CUDTGroup::sendBalancing(const char* buf, int len, SRT_MSGCTRL& w_mc) +{ + return sendSelectable(buf, len, (w_mc), true); +} + +int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool use_select) { // Avoid stupid errors in the beginning. if (len <= 0) @@ -1049,6 +1064,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) vector wipeme; vector idleLinks; vector pendingSockets; // need sock ids as it will be checked out of lock + map linkIndexMap; int32_t curseq = SRT_SEQNO_NONE; // The seqno of the first packet of this message. int32_t nextseq = SRT_SEQNO_NONE; // The seqno of the first packet of next message. @@ -1167,42 +1183,26 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) if (w_mc.srctime == 0) w_mc.srctime = count_microseconds(steady_clock::now().time_since_epoch()); - for (vector::iterator snd = activeLinks.begin(); snd != activeLinks.end(); ++snd) + BalancingLinkState lstate = { m_Group.active(), 0, 0 }; + gli_t selink = m_Group.end(); + + void* member_marker = NULL; + if (use_select) { - gli_t d = *snd; - int erc = 0; // success - // Remaining sndstate is SRT_GST_RUNNING. Send a payload through it. - try + selink = CALLBACK_CALL(m_cbSelectLink, lstate); + if (selink == m_Group.end()) { - // This must be wrapped in try-catch because on error it throws an exception. - // Possible return values are only 0, in case when len was passed 0, or a positive - // >0 value that defines the size of the data that it has sent, that is, in case - // of Live mode, equal to 'len'. - stat = d->ps->core().sendmsg2(buf, len, (w_mc)); - } - catch (CUDTException& e) - { - cx = e; - stat = -1; - erc = e.getErrorCode(); - } + // If this returns the "trap" link index, it means + // that no link is qualified for sending. - if (stat != -1) - { - curseq = w_mc.pktseq; - nextseq = d->ps->core().schedSeqNo(); + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } - const Sendstate cstate = {d->id, &*d, stat, erc}; - sendstates.push_back(cstate); - d->sndresult = stat; - d->laststatus = d->ps->getStatus(); + // Now, we will use 'selink' to set the member socket + // identification to the packet sent to EVERY link. + member_marker = &*selink; } - // Ok, we have attempted to send a payload over all links - // that are currently in the RUNNING state. We know that at - // least one is successful if we have non-default curseq value. - // Here we need to activate all links that are found as IDLE. // Some portion of logical exclusions: // @@ -1268,7 +1268,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) try { - stat = d->ps->core().sendmsg2(buf, len, (w_mc)); + stat = d->ps->core().sendMessageInternal(buf, len, member_marker, (w_mc)); } catch (CUDTException& e) { @@ -1296,6 +1296,45 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) sendstates.push_back(cstate); } + // } + + + for (vector::iterator snd = activeLinks.begin(); snd != activeLinks.end(); ++snd) + { + gli_t d = *snd; + int erc = 0; // success + // Remaining sndstate is SRT_GST_RUNNING. Send a payload through it. + try + { + // This must be wrapped in try-catch because on error it throws an exception. + // Possible return values are only 0, in case when len was passed 0, or a positive + // >0 value that defines the size of the data that it has sent, that is, in case + // of Live mode, equal to 'len'. + stat = d->ps->core().sendMessageInternal(buf, len, member_marker, (w_mc)); + } + catch (CUDTException& e) + { + cx = e; + stat = -1; + erc = e.getErrorCode(); + } + + if (stat != -1) + { + curseq = w_mc.pktseq; + nextseq = d->ps->core().schedSeqNo(); + } + + const Sendstate cstate = {d->id, &*d, stat, erc}; + sendstates.push_back(cstate); + d->sndresult = stat; + d->laststatus = d->ps->getStatus(); + } + + // Ok, we have attempted to send a payload over all links + // that are currently in the RUNNING state. We know that at + // least one is successful if we have non-default curseq value. + if (nextseq != SRT_SEQNO_NONE) { HLOGC(gslog.Debug, @@ -1303,8 +1342,6 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) m_iLastSchedSeqNo = nextseq; } - // } - // { send_CheckBrokenSockets() if (!pendingSockets.empty()) @@ -1581,7 +1618,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) // Possible return values are only 0, in case when len was passed 0, or a positive // >0 value that defines the size of the data that it has sent, that is, in case // of Live mode, equal to 'len'. - stat = d->ps->core().sendmsg2(buf, len, (w_mc)); + stat = d->ps->core().sendMessageInternal(buf, len, member_marker, (w_mc)); } catch (CUDTException& e) { @@ -4437,6 +4474,371 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) return stat; } +int CUDTGroup::sendBalancing_orig(const char* buf, int len, SRT_MSGCTRL& w_mc) +{ + // Avoid stupid errors in the beginning. + if (len <= 0) + { + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + // NOTE: This is a "vector of list iterators". Every element here + // is an iterator to another container. + // Note that "list" is THE ONLY container in standard C++ library, + // for which NO ITERATORS ARE INVALIDATED after a node at particular + // iterator has been removed, except for that iterator itself. + vector wipeme; + vector pending; + + w_mc.msgno = -1; + + ScopedLock guard (m_GroupLock); + + // Always set the same exactly message number for the payload + // sent over all links.Regardless whether it will be used to synchronize + // the streams or not. + if (m_iLastSchedMsgNo != -1) + { + HLOGC(gslog.Debug, log << "grp/sendBalancing: setting message number: " << m_iLastSchedMsgNo); + w_mc.msgno = m_iLastSchedMsgNo; + } + else + { + HLOGP(gslog.Debug, "grp/sendBalancing: NOT setting message number - waiting for the first successful sending"); + } + + + // Overview loop + for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d) + { + d->sndresult = 0; // set as default + + // Check socket sndstate before sending + if (d->sndstate == SRT_GST_BROKEN) + { + HLOGC(gslog.Debug, log << "grp/sendBalancing: socket in BROKEN state: @" << d->id << ", sockstatus=" << SockStatusStr(d->ps ? d->ps->getStatus() : SRTS_NONEXIST)); + wipeme.push_back(d); + d->sndresult = -1; + + /* + This distinction is now blocked - it has led to blocking removal of + authentically broken sockets that just got only incorrect state update. + (XXX This problem has to be fixed either, but when epoll is rewritten it + will be fixed from the start anyway). + + // Check if broken permanently + if (!d->ps || d->ps->getStatus() == SRTS_BROKEN) + { + HLOGC(gslog.Debug, log << "... permanently. Will delete it from group $" << id()); + wipeme.push_back(d); + } + else + { + HLOGC(gslog.Debug, log << "... socket still " << SockStatusStr(d->ps ? d->ps->getStatus() : SRTS_NONEXIST)); + } + */ + continue; + } + + if (d->sndstate == SRT_GST_IDLE) + { + SRT_SOCKSTATUS st = SRTS_NONEXIST; + if (d->ps) + st = d->ps->getStatus(); + // If the socket is already broken, move it to broken. + if (int(st) >= int(SRTS_BROKEN)) + { + HLOGC(gslog.Debug, log << "CUDTGroup::send.$" << id() << ": @" << d->id << " became " + << SockStatusStr(st) << ", WILL BE CLOSED."); + wipeme.push_back(d); + d->sndstate = SRT_GST_BROKEN; + d->sndresult = -1; + continue; + } + + if (st != SRTS_CONNECTED) + { + HLOGC(gslog.Debug, log << "CUDTGroup::send. @" << d->id << " is still " << SockStatusStr(st) << ", skipping."); + pending.push_back(d); + continue; + } + + HLOGC(gslog.Debug, log << "grp/sendBalancing: socket in IDLE state: @" << d->id << " - ACTIVATING it"); + d->sndstate = SRT_GST_RUNNING; + continue; + } + + if (d->sndstate == SRT_GST_RUNNING) + { + HLOGC(gslog.Debug, log << "grp/sendBalancing: socket in RUNNING state: @" << d->id << " - will send a payload"); + continue; + } + + HLOGC(gslog.Debug, log << "grp/sendBalancing: socket @" << d->id << " not ready, state: " + << StateStr(d->sndstate) << "(" << int(d->sndstate) << ") - NOT sending, SET AS PENDING"); + + pending.push_back(d); + } + + SRT_ATR_UNUSED CUDTException cx (MJ_SUCCESS, MN_NONE, 0); + BalancingLinkState lstate = { m_Group.active(), 0, 0 }; + int stat = -1; + gli_t selink; // will be initialized first in the below loop + + for (;;) + { + // Repeatable block. + // The algorithm is more-less: + // + // 1. Select a link to use for sending + // 2. Perform the operation + // 3. If the operation succeeded, record this link and exit with success + // 4. If the operation failed, call selector again, this time with error info + // 5. The selector can return a link to use again, or gli_NULL() if the operation should fail + // 6. If the selector returned a valid link, go to p. 2. + + // Call selection. Default: defaultSelectLink + selink = CALLBACK_CALL(m_cbSelectLink, lstate); + + if (selink == m_Group.end()) + { + stat = -1; // likely not possible, but make sure. + break; + } + + // Sanity check + if (selink->sndstate != SRT_GST_RUNNING) + { + LOGC(gslog.Error, log << "IPE: sendBalancing: selectLink returned an iactive link! - trying blindly anyway"); + } + + // Perform the operation + int erc = SRT_SUCCESS; + try + { + // This must be wrapped in try-catch because on error it throws an exception. + // Possible return values are only 0, in case when len was passed 0, or a positive + // >0 value that defines the size of the data that it has sent, that is, in case + // of Live mode, equal to 'len'. + CUDTSocket* ps = selink->ps; + InvertedLock ug (m_GroupLock); + + HLOGC(gslog.Debug, log << "grp/sendBalancing: SENDING #" << w_mc.msgno << " through link [" << m_uBalancingRoll << "]"); + + // NOTE: EXCEPTION PASSTHROUGH. + stat = ps->core().sendmsg2(buf, len, (w_mc)); + } + catch (CUDTException& e) + { + cx = e; + stat = -1; + erc = e.getErrorCode(); + } + + selink->sndresult = stat; + + if (stat != -1) + { + if (m_iLastSchedMsgNo == -1) + { + // Initialize this number + HLOGC(gslog.Debug, log << "grp/sendBalancing: INITIALIZING message number: " << w_mc.msgno); + m_iLastSchedMsgNo = w_mc.msgno; + } + + m_Group.set_active(selink); + + // Sending succeeded. Complete the rest of the activities. + break; + } + + // Handle the error. If a link got the blocking error, set + // this link PENDING state. This will cause that this link be + // activated at the next sending call and retried, but in this + // session it will be skipped. + if (erc == SRT_EASYNCSND) + { + selink->sndstate = SRT_GST_PENDING; + } + else + { + selink->sndstate = SRT_GST_BROKEN; + if (std::find(wipeme.begin(), wipeme.end(), selink) == wipeme.end()) + wipeme.push_back(selink); // unique add + } + + lstate.ilink = selink; + lstate.status = stat; + lstate.errorcode = erc; + + // Now repeat selection. + // Note that every selection either gets a link that + // succeeds (and this loop is broken) or the link becomes + // broken, and then it should be skipped by the selector. + // Eventually with all links broken the selector will return + // no link to be used, and therefore this operation is interrupted + // and error-reported. + } + + if (!pending.empty()) + { + HLOGC(gslog.Debug, log << "grp/sendBalancing: found pending sockets, polling them."); + + // These sockets if they are in pending state, they should be added to m_SndEID + // at the connecting stage. + CEPoll::fmap_t sready; + + if (m_Global.m_EPoll.empty(*m_SndEpolld)) + { + // Sanity check - weird pending reported. + LOGC(gslog.Error, log << "grp/sendBalancing: IPE: reported pending sockets, but EID is empty - wiping pending!"); + copy(pending.begin(), pending.end(), back_inserter(wipeme)); + } + else + { + { + InvertedLock ug (m_GroupLock); + m_Global.m_EPoll.swait(*m_SndEpolld, sready, 0, false /*report by retval*/); // Just check if anything happened + } + + HLOGC(gslog.Debug, log << "grp/sendBalancing: RDY: " << DisplayEpollResults(sready)); + + // sockets in EX: should be moved to wipeme. + for (vector::iterator i = pending.begin(); i != pending.end(); ++i) + { + gli_t d = *i; + int rdev = CEPoll::ready(sready, d->id); + if (rdev & SRT_EPOLL_ERR) + { + HLOGC(gslog.Debug, log << "grp/sendBalancing: Socket @" << d->id << " reported FAILURE - moved to wiped."); + // Failed socket. Move d to wipeme. Remove from eid. + wipeme.push_back(d); + m_Global.epoll_remove_usock(m_SndEID, d->id); + } + else if (rdev & SRT_EPOLL_OUT) + { + d->sndstate = SRT_GST_IDLE; + } + } + + // After that, all sockets that have been reported + // as ready to write should be removed from EID. This + // will also remove those sockets that have been added + // as redundant links at the connecting stage and became + // writable (connected) before this function had a chance + // to check them. + m_Global.m_EPoll.clear_ready_usocks(*m_SndEpolld, SRT_EPOLL_CONNECT); + } + } + + + // Do final checkups. + + // Now complete the status data in the function and return. + // This is the case for both successful and failed return. + + size_t grpsize = m_Group.size(); + + if (w_mc.grpdata_size < grpsize) + { + w_mc.grpdata = NULL; + } + + size_t i = 0; + + // Fill the array first before removal. + + bool ready_again = false; + for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d, ++i) + { + if (w_mc.grpdata) + { + // Enough space to fill + w_mc.grpdata[i].id = d->id; + w_mc.grpdata[i].sockstate = d->laststatus; + + if (d->sndstate == SRT_GST_RUNNING) + w_mc.grpdata[i].result = d->sndresult; + else if (d->sndstate == SRT_GST_IDLE) + w_mc.grpdata[i].result = 0; + else + w_mc.grpdata[i].result = -1; + + memcpy(&w_mc.grpdata[i].peeraddr, &d->peer, d->peer.size()); + } + + // We perform this loop anyway because we still need to check if any + // socket is writable. Note that the group lock will hold any write ready + // updates that are performed just after a single socket update for the + // group, so if any socket is actually ready at the moment when this + // is performed, and this one will result in none-write-ready, this will + // be fixed just after returning from this function. + + ready_again = ready_again | d->ps->writeReady(); + } + + // Review the wipeme sockets. + // The reason why 'wipeme' is kept separately to 'broken_sockets' is that + // it might theoretically happen that ps becomes NULL while the item still exists. + vector broken_sockets; + + // delete all sockets that were broken at the entrance + for (vector::iterator i = wipeme.begin(); i != wipeme.end(); ++i) + { + gli_t d = *i; + CUDTSocket* ps = d->ps; + if (!ps) + { + LOGC(gslog.Error, log << "grp/sendBalancing: IPE: socket NULL at id=" << d->id << " - removing from group list"); + // Closing such socket is useless, it simply won't be found in the map and + // the internal facilities won't know what to do with it anyway. + // Simply delete the entry. + m_Group.erase(d); + continue; + } + broken_sockets.push_back(ps); + } + + if (!broken_sockets.empty()) // Prevent unlock-lock cycle if no broken sockets found + { + // Lift the group lock for a while, to avoid possible deadlocks. + InvertedLock ug (m_GroupLock); + + for (vector::iterator x = broken_sockets.begin(); x != broken_sockets.end(); ++x) + { + CUDTSocket* ps = *x; + HLOGC(gslog.Debug, log << "grp/sendBalancing: BROKEN SOCKET @" << ps->m_SocketID << " - CLOSING AND REMOVING."); + + // NOTE: This does inside: ps->removeFromGroup(). + // After this call, 'd' is no longer valid and *i is singular. + CUDT::uglobal().close(ps->m_SocketID); + } + } + + HLOGC(gslog.Debug, log << "grp/sendBalancing: - wiped " << wipeme.size() << " broken sockets"); + + w_mc.grpdata_size = i; + + if (!ready_again) + { + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); + } + + // If m_iLastSchedSeqNo wasn't initialized above, don't touch it. + if (m_iLastSchedMsgNo != -1) + { + m_iLastSchedMsgNo = ++MsgNo(m_iLastSchedMsgNo); + HLOGC(gslog.Debug, log << "grp/sendBalancing: updated msgno: " << m_iLastSchedMsgNo); + } + + if (stat == -1) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + return stat; +} + + +// XXX DEAD CODE. // [[using locked(CUDTGroup::m_GroupLock)]]; void CUDTGroup::ackMessage(int32_t msgno) { diff --git a/srtcore/group.h b/srtcore/group.h index eb358dd9a..e0ff010ca 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -208,9 +208,16 @@ class CUDTGroup void setGroupConnected(); - int send(const char* buf, int len, SRT_MSGCTRL& w_mc); - int sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc); - int sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc); + int send(const char* buf, int len, SRT_MSGCTRL& w_mc); + int sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc); + int sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc); + int sendBalancing(const char* buf, int len, SRT_MSGCTRL& w_mc); + + // XXX deprecated code + int sendBalancing_orig(const char* buf, int len, SRT_MSGCTRL& w_mc); + + int sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool use_select); + static int32_t generateISN(); private: @@ -687,6 +694,49 @@ class CUDTGroup sync::Mutex m_RcvDataLock; sync::atomic m_iLastSchedSeqNo; // represetnts the value of CUDT::m_iSndNextSeqNo for each running socket sync::atomic m_iLastSchedMsgNo; + + // Fields required for balancing groups +#if ENABLE_NEW_RCVBUFFER + unsigned int m_uBalancingRoll; + + /// This is initialized with some number that should be + /// decreased with every packet sent. Any decision and + /// analysis for a decision concerning bonding group behavior + /// should be taken only when this value is 0. During some + /// of the analysis steps this value may be reset to some + /// higer value so that for particular number of packets + /// no analysis is being done (this prevents taking measurement + /// data too early when the number of collected data was + /// too little and therefore any average is little reliable). + unsigned int m_RandomCredit; + + struct BalancingLinkState + { + gli_t ilink; // previously used link + int status; // 0 = normal first entry; -1 = repeated selection + int errorcode; + }; + typedef gli_t selectLink_cb(void*, const BalancingLinkState&); + CallbackHolder m_cbSelectLink; + + // Plain algorithm: simply distribute the load + // on all links equally. + gli_t linkSelect_plain(const BalancingLinkState&); + static gli_t linkSelect_plain_fw(void* opaq, const BalancingLinkState& st) + { + CUDTGroup* g = (CUDTGroup*)opaq; + return g->linkSelect_plain(st); + } + + // Window algorihm: keep balance, but + gli_t linkSelect_window(const BalancingLinkState&); + static gli_t linkSelect_window_fw(void* opaq, const BalancingLinkState& st) + { + CUDTGroup* g = (CUDTGroup*)opaq; + return g->linkSelect_window(st); + } +#endif + // Statistics struct Stats diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index f47734916..9bf9c2934 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -533,6 +533,25 @@ bool srt::CSndQueue::getBind(char* dst, size_t len) const } #endif +#if defined(SRT_DEBUG_SNDQ_HIGHRATE) +static void CSndQueueDebugHighratePrint(const CSndQueue* self, const steady_clock::time_point currtime) +{ + if (self->m_ullDbgTime <= currtime) + { + fprintf(stdout, + "SndQueue %lu slt:%lu nrp:%lu snt:%lu nrt:%lu ctw:%lu\n", + self->m_WorkerStats.lIteration, + self->m_WorkerStats.lSleepTo, + self->m_WorkerStats.lNotReadyPop, + self->m_WorkerStats.lSendTo, + self->m_WorkerStats.lNotReadyTs, + self->m_WorkerStats.lCondWait); + memset(&self->m_WorkerStats, 0, sizeof(self->m_WorkerStats)); + self->m_ullDbgTime = currtime + self->m_ullDbgPeriod; + } +} +#endif + void* srt::CSndQueue::worker(void* param) { CSndQueue* self = (CSndQueue*)param; @@ -544,34 +563,30 @@ void* srt::CSndQueue::worker(void* param) #endif #if defined(SRT_DEBUG_SNDQ_HIGHRATE) +#define IF_DEBUG_HIGHRATE(statement) statement CTimer::rdtsc(self->m_ullDbgTime); self->m_ullDbgPeriod = uint64_t(5000000) * CTimer::getCPUFrequency(); self->m_ullDbgTime += self->m_ullDbgPeriod; +#else +#define IF_DEBUG_HIGHRATE(statement) (void)0 #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ while (!self->m_bClosing) { const steady_clock::time_point next_time = self->m_pSndUList->getNextProcTime(); -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lIteration++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lIteration++); if (is_zero(next_time)) { -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lNotReadyTs++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lNotReadyTs++); // wait here if there is no sockets with data to be sent THREAD_PAUSED(); if (!self->m_bClosing) { self->m_pSndUList->waitNonEmpty(); - -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lCondWait++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lCondWait++); } THREAD_RESUMED(); @@ -581,43 +596,23 @@ void* srt::CSndQueue::worker(void* param) // wait until next processing time of the first socket on the list const steady_clock::time_point currtime = steady_clock::now(); -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - if (self->m_ullDbgTime <= currtime) - { - fprintf(stdout, - "SndQueue %lu slt:%lu nrp:%lu snt:%lu nrt:%lu ctw:%lu\n", - self->m_WorkerStats.lIteration, - self->m_WorkerStats.lSleepTo, - self->m_WorkerStats.lNotReadyPop, - self->m_WorkerStats.lSendTo, - self->m_WorkerStats.lNotReadyTs, - self->m_WorkerStats.lCondWait); - memset(&self->m_WorkerStats, 0, sizeof(self->m_WorkerStats)); - self->m_ullDbgTime = currtime + self->m_ullDbgPeriod; - } -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ - - THREAD_PAUSED(); + IF_DEBUG_HIGHRATE(CSndQueueDebugHighratePrint(self, currtime)); if (currtime < next_time) { + THREAD_PAUSED(); self->m_pTimer->sleep_until(next_time); - -#if defined(HAI_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lSleepTo++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + THREAD_RESUMED(); + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lSleepTo++); } - THREAD_RESUMED(); // Get a socket with a send request if any. CUDT* u = self->m_pSndUList->pop(); if (u == NULL) { -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lNotReadyPop++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lNotReadyPop++); continue; } - + #define UST(field) ((u->m_b##field) ? "+" : "-") << #field << " " HLOGC(qslog.Debug, log << "CSndQueue: requesting packet from @" << u->socketID() << " STATUS: " << UST(Listening) @@ -627,36 +622,30 @@ void* srt::CSndQueue::worker(void* param) if (!u->m_bConnected || u->m_bBroken) { -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lNotReadyPop++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lNotReadyPop++); continue; } // pack a packet from the socket CPacket pkt; - const std::pair res_time = u->packData((pkt)); + steady_clock::time_point next_send_time; + bool valid = u->packData((pkt), (next_send_time)); // Check if payload size is invalid. - if (res_time.first == false) + if (!valid) { -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lNotReadyPop++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lNotReadyPop++); continue; } const sockaddr_any addr = u->m_PeerAddr; - const steady_clock::time_point next_send_time = res_time.second; if (!is_zero(next_send_time)) self->m_pSndUList->update(u, CSndUList::DO_RESCHEDULE, next_send_time); HLOGC(qslog.Debug, log << self->CONID() << "chn:SENDING: " << pkt.Info()); self->m_pChannel->sendto(addr, pkt); -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lSendTo++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lSendTo++); } THREAD_EXIT(); diff --git a/srtcore/queue.h b/srtcore/queue.h index 0680fc8e0..29fb80c0a 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -68,6 +68,8 @@ namespace srt class CChannel; class CUDT; +class CUnitQueue; + struct CUnit { CPacket m_Packet; // packet @@ -80,6 +82,7 @@ struct CUnit }; Flag m_iFlag; // 0: free, 1: occupied, 2: msg read but not freed (out-of-order), 3: msg dropped // TODO: Transition to the new RcvBuffer allows to use bool here. + CUnitQueue* m_pParentQueue; }; class CUnitQueue diff --git a/srtcore/srt.h b/srtcore/srt.h index c5f60d752..b83b53c87 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -943,6 +943,7 @@ typedef enum SRT_GROUP_TYPE SRT_GTYPE_UNDEFINED, SRT_GTYPE_BROADCAST, SRT_GTYPE_BACKUP, + SRT_GTYPE_BALANCING, // ... SRT_GTYPE_E_END } SRT_GROUP_TYPE; diff --git a/srtcore/srt_attr_defs.h b/srtcore/srt_attr_defs.h index ee4c85b0d..2b7e07d3d 100644 --- a/srtcore/srt_attr_defs.h +++ b/srtcore/srt_attr_defs.h @@ -82,6 +82,15 @@ used by SRT library internally. #error "The currently compiled application required C++11, but your compiler doesn't support it." #endif +// Macro shortcut for implementing parts of the redundancy features +// that require the new receiver buffer + +#if defined(ENABLE_BONDING) && defined(ENABLE_NEW_RCVBUFFER) +#define ENABLE_NEW_BONDING 1 +#else +#define ENABLE_NEW_BONDING 0 +#endif + /////////////////////////////////////////////////////////////////////////////// // Attributes for thread safety analysis // - Clang TSA (https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#mutexheader). diff --git a/srtcore/utilities.h b/srtcore/utilities.h index 31e05b205..8c333d5e7 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -748,6 +748,12 @@ inline void insert_uniq(std::vector& v, const ArgValue& val) v.push_back(val); } +template +inline std::pair Tie(Type1& var1, Type2& var2) +{ + return std::pair(var1, var2); +} + template struct CallbackHolder { @@ -1058,11 +1064,11 @@ inline ValueType avg_iir_w(ValueType old_value, ValueType new_value, size_t new_ // This relies only on a convention, which is the following: // // V x = object.prop(); <-- get the property's value -// object.prop(x); <-- set the property a value +// object.set_prop(x); <-- set the property a value // // Properties might be also chained when setting: // -// object.prop1(v1).prop2(v2).prop3(v3); +// object.set_prop1(v1).set_prop2(v2).set_prop3(v3); // // Properties may be defined various even very complicated // ways, which is simply providing a method with body. In order From 7fbca8ad1e93cc6bed961ca4c51046c9a2d04f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 1 Aug 2022 17:33:17 +0200 Subject: [PATCH 02/62] Completed balancing code (compiled, not tested) --- apps/socketoptions.hpp | 1 + srtcore/core.cpp | 5 + srtcore/core.h | 2 + srtcore/group.cpp | 343 ++++++++++++++++++++++++++++++++++++++- srtcore/group.h | 2 + srtcore/group_common.cpp | 2 + srtcore/group_common.h | 4 + srtcore/srt.h | 1 + 8 files changed, 358 insertions(+), 2 deletions(-) diff --git a/apps/socketoptions.hpp b/apps/socketoptions.hpp index af91aa8ec..1835dfd6b 100644 --- a/apps/socketoptions.hpp +++ b/apps/socketoptions.hpp @@ -250,6 +250,7 @@ const SocketOption srt_options [] { #if ENABLE_BONDING { "groupconnect", 0, SRTO_GROUPCONNECT, SocketOption::PRE, SocketOption::INT, nullptr}, { "groupminstabletimeo", 0, SRTO_GROUPMINSTABLETIMEO, SocketOption::PRE, SocketOption::INT, nullptr}, + { "groupconfig", 0, SRTO_GROUPCONFIG, SocketOption::PRE, SocketOption::STRING, nullptr}, #endif #ifdef SRT_ENABLE_BINDTODEVICE { "bindtodevice", 0, SRTO_BINDTODEVICE, SocketOption::PRE, SocketOption::STRING, nullptr}, diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 490cb5d22..f63f647b9 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -295,6 +295,9 @@ void srt::CUDT::construct() m_bGroupTsbPd = false; m_bPeerTLPktDrop = false; + m_iSndMinFlightSpan = -1; // -1 value means "not measured". Normally all current values of -1 are rejected. + // (note that flight == 0 is still a valid value) + // Initilize mutex and condition variables. initSynch(); @@ -8344,6 +8347,7 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ ScopedLock ack_lock(m_RecvAckLock); m_iFlowWindowSize = m_iFlowWindowSize - CSeqNo::seqoff(m_iSndLastAck, ackdata_seqno); m_iSndLastAck = ackdata_seqno; + m_iSndMinFlightSpan = getFlightSpan(); // TODO: m_tsLastRspAckTime should be protected with m_RecvAckLock // because the sendmsg2 may want to change it at the same time. @@ -8396,6 +8400,7 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ // Update Flow Window Size, must update before and together with m_iSndLastAck m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; m_iSndLastAck = ackdata_seqno; + m_iSndMinFlightSpan = getFlightSpan(); m_tsLastRspAckTime = currtime; m_iReXmitCount = 1; // Reset re-transmit count since last ACK } diff --git a/srtcore/core.h b/srtcore/core.h index 2188cf2a2..d34cb0124 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -890,6 +890,8 @@ class CUDT void setInitialRcvSeq(int32_t isn); + sync::atomic m_iSndMinFlightSpan; // updated with every ACK, number of packets in flight at ACK + int32_t m_iISN; // Initial Sequence Number bool m_bPeerTsbPd; // Peer accept TimeStamp-Based Rx mode bool m_bPeerTLPktDrop; // Enable sender late packet dropping diff --git a/srtcore/group.cpp b/srtcore/group.cpp index c60011bfe..0aea6e5e1 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -294,7 +294,9 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) // two or more sockets start arguing about it. m_iLastSchedSeqNo = CUDT::generateISN(); +#if ENABLE_NEW_RCVBUFFER m_cbSelectLink.set(this, &CUDTGroup::linkSelect_plain_fw); +#endif } CUDTGroup::~CUDTGroup() @@ -406,6 +408,12 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) LOGP(gmlog.Error, "group option: SRTO_CONGESTION is only allowed as 'live' and cannot be changed"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); +#if ENABLE_NEW_RCVBUFFER + case SRTO_GROUPCONFIG: + configure((const char*)optval); + return; +#endif + default: break; } @@ -1048,7 +1056,7 @@ int CUDTGroup::sendBalancing(const char* buf, int len, SRT_MSGCTRL& w_mc) return sendSelectable(buf, len, (w_mc), true); } -int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool use_select) +int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool use_select SRT_ATR_UNUSED) { // Avoid stupid errors in the beginning. if (len <= 0) @@ -1183,10 +1191,12 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool if (w_mc.srctime == 0) w_mc.srctime = count_microseconds(steady_clock::now().time_since_epoch()); + void* member_marker = NULL; +#if ENABLE_NEW_RCVBUFFER + BalancingLinkState lstate = { m_Group.active(), 0, 0 }; gli_t selink = m_Group.end(); - void* member_marker = NULL; if (use_select) { selink = CALLBACK_CALL(m_cbSelectLink, lstate); @@ -1202,6 +1212,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool // identification to the packet sent to EVERY link. member_marker = &*selink; } +#endif // Here we need to activate all links that are found as IDLE. // Some portion of logical exclusions: @@ -4474,6 +4485,7 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) return stat; } +#if ENABLE_NEW_RCVBUFFER int CUDTGroup::sendBalancing_orig(const char* buf, int len, SRT_MSGCTRL& w_mc) { // Avoid stupid errors in the beginning. @@ -4836,6 +4848,7 @@ int CUDTGroup::sendBalancing_orig(const char* buf, int len, SRT_MSGCTRL& w_mc) return stat; } +#endif // XXX DEAD CODE. @@ -5120,6 +5133,332 @@ void CUDTGroup::updateFailedLink() } } +#if ENABLE_NEW_RCVBUFFER + +int CUDTGroup::configure(const char* str) +{ + string config = str; + switch (type()) + { + case SRT_GTYPE_BALANCING: + // config contains the algorithm name + if (config == "" || config == "plain") + { + m_cbSelectLink.set(this, &CUDTGroup::linkSelect_plain_fw); + HLOGC(gmlog.Debug, log << "group(balancing): PLAIN algorithm selected"); + } + else if (config == "window") + { + m_cbSelectLink.set(this, &CUDTGroup::linkSelect_window_fw); + HLOGC(gmlog.Debug, log << "group(balancing): WINDOW algorithm selected"); + } + else + { + LOGC(gmlog.Error, log << "group(balancing): unknown selection algorithm '" + << config << "'"); + return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); + } + + break; + + default: + if (config == "") + { + // You can always call the config with empty string, + // it should set defaults or do nothing, if not supported. + return 0; + } + LOGC(gmlog.Error, log << "this group type doesn't support any configuration"); + return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); + } + + return 0; +} + + +CUDTGroup::gli_t CUDTGroup::linkSelect_plain(const CUDTGroup::BalancingLinkState& state) +{ + if (state.ilink == m_Group.end()) + { + // Very first sending operation. Pick up the first link + return m_Group.begin(); + } + + gli_t this_link = state.ilink; + + for (;;) + { + // Roll to the next link + ++this_link; + if (this_link == m_Group.end()) + this_link = m_Group.begin(); // roll around + + // Check the status. If the link is PENDING or BROKEN, + // skip it. If the link is IDLE, turn it to ACTIVE. + // If the rolling reached back to the original link, + // and this one isn't usable either, return m_Group.end(). + + if (this_link->sndstate == SRT_GST_IDLE) + this_link->sndstate = SRT_GST_RUNNING; + + if (this_link->sndstate == SRT_GST_RUNNING) + { + // Found you, buddy. Go on. + return this_link; + } + + if (this_link == state.ilink) + { + // No more links. Sorry. + return m_Group.end(); + } + + // Check maybe next link... + } + + return this_link; +} + +struct LinkCapableData +{ + CUDTGroup::gli_t link; + int flight; +}; + +CUDTGroup::gli_t CUDTGroup::linkSelect_window(const CUDTGroup::BalancingLinkState& state) +{ + if (state.ilink == m_Group.end()) + { + // Very first sending operation. Pick up the first link + return m_Group.begin(); + } + + + gli_t this_link = m_Group.end(); + + if (m_RandomCredit <= 0) + { + vector linkdata; + int total_flight = 0; + int number_links = 0; + + // First, collect data required for selection + vector linkorder; + + gli_t last = state.ilink; + ++last; + // NOTE: ++last could make it == m_Group.end() in which + // case the first loop will get 0 passes and the second + // one will be from begin() to end(). + for (gli_t li = last; li != m_Group.end(); ++li) + linkorder.push_back(li); + for (gli_t li = m_Group.begin(); li != last; ++li) + linkorder.push_back(li); + + // Sanity check + if (linkorder.empty()) + { + LOGC(gslog.Error, log << "linkSelect_window: IPE: no links???"); + return m_Group.end(); + } + + // Fallback + this_link = *linkorder.begin(); + + // This does the following: + // We have links: [ 1 2 3 4 5 ] + // Last used link was 4 + // linkorder: [ (5) (1) (2) (3) (4) ] + for (vector::iterator i = linkorder.begin(); i != linkorder.end(); ++i) + { + gli_t li = *i; + int flight = li->ps->core().m_iSndMinFlightSpan; + + HLOGC(gslog.Debug, log << "linkSelect_window: previous link was #" << distance(m_Group.begin(), state.ilink) + << " Checking link #" << distance(m_Group.begin(), li) + << "@" << li->id << " TO " << li->peer.str() + << " flight=" << flight); + + // Upgrade idle to running + if (li->sndstate == SRT_GST_IDLE) + li->sndstate = SRT_GST_RUNNING; + + if (li->sndstate != SRT_GST_RUNNING) + { + HLOGC(gslog.Debug, log << "linkSelect_window: ... state=" << StateStr(li->sndstate) << " - skipping"); + // Skip pending/broken links + continue; + } + + // Check if this link was used at least once so far. + // If not, select it immediately. + if (li->load_factor == 0) + { + HLOGC(gslog.Debug, log << "linkSelect_window: ... load factor empty: SELECTING."); + this_link = li; + goto ReportLink; + } + + ++number_links; + if (flight == -1) + { + HLOGC(gslog.Debug, log << "linkSelect_window: link #" << distance(m_Group.begin(), this_link) + << " HAS NO FLIGHT COUNTED - selecting, deferring to next 18 * numberlinks=" << number_links << " packets."); + // Not measureable flight. Use this link. + this_link = li; + + // Also defer next measurement point by 16 per link. + // Of course, number_links doesn't contain the exact + // number of active links (the loop is underway), but + // it doesn't matter much. The probability is on the + // side of later links, so it's unlikely that earlier + // links could enforce more often update (worst case + // scenario, the probing will happen again in 16 packets). + m_RandomCredit = 16 * number_links; + + goto ReportLink; + } + flight += 2; // prevent having 0 used for equations + + total_flight += flight; + + linkdata.push_back( (LinkCapableData){li, flight} ); + } + + if (linkdata.empty()) + { + HLOGC(gslog.Debug, log << "linkSelect_window: no capable links found - requesting transmission interrupt!"); + return m_Group.end(); + } + + this_link = linkdata.begin()->link; + double least_load = linkdata.begin()->link->load_factor; + double biggest_unit_load = 0; + + HLOGC(gslog.Debug, log << "linkSelect_window: total_flight (with fix): " << total_flight + << " - updating link load factors:"); + // Now that linkdata list is ready, update the link span values + // If at least one link has the span value not yet measureable + for (vector::iterator i = linkdata.begin(); + i != linkdata.end(); ++i) + { + // Here update the unit load basing on the percentage + // of the link flight size. + // + // The sum of all flight window sizes from all links is + // the total number. The value of the flight size for + // each link shows how much of a percentage this link + // has as share. + // + // Example: in case when all links go totally equally, + // and there is 5 links, each having 10 packets in flight: + // + // total_flitht = 50 + // share_load = link_flight / total_flight = 10/50 = 1/5 + // link_load = share_load * number_links = 1/5 * 5 = 1.0 + // + // If the links are not perfectly equivalent, some deviation + // towards 1.0 will result. + double share_load = double(i->flight) / total_flight; + double link_load = share_load * number_links; + i->link->unit_load = link_load; + + HLOGC(gslog.Debug, log << "linkSelect_window: ... #" << distance(m_Group.begin(), i->link) + << " flight=" << i->flight << " share_load=" << (100*share_load) << "% unit-load=" + << link_load << " current-load:" << i->link->load_factor); + + if (link_load > biggest_unit_load) + biggest_unit_load = link_load; + + if (i->link->load_factor < least_load) + { + HLOGC(gslog.Debug, log << "linkSelect_window: ... this link has currently smallest load"); + this_link = i->link; + least_load = i->link->load_factor; + } + } + + HLOGC(gslog.Debug, log << "linkSelect_window: selecting link #" << distance(m_Group.begin(), this_link)); + // Now that a link is selected and all load factors updated, + // do a CUTOFF by the value of at least one size of unit load. + + + // This comparison can be used to recognize if all values of + // the load factor have already exceeded the value that should + // result in a cutoff. + if (biggest_unit_load > 0 && least_load > 2 * biggest_unit_load) + { + for (vector::iterator i = linkdata.begin(); + i != linkdata.end(); ++i) + { + i->link->load_factor -= biggest_unit_load; + } + HLOGC(gslog.Debug, log << "linkSelect_window: cutting off value of " << biggest_unit_load + << " from all load factors"); + } + + // The above loop certainly found something. + goto ReportLink; + } + + HLOGC(gslog.Debug, log << "linkSelect_window: remaining credit: " << m_RandomCredit + << " - staying with equal balancing"); + + // This starts from 16, decreases here. As long as + // there is a credit given, simply roll over all links + // equally. + --m_RandomCredit; + + this_link = state.ilink; + for (;;) + { + // Roll to the next link + ++this_link; + if (this_link == m_Group.end()) + this_link = m_Group.begin(); // roll around + + // Check the status. If the link is PENDING or BROKEN, + // skip it. If the link is IDLE, turn it to ACTIVE. + // If the rolling reached back to the original link, + // and this one isn't usable either, return m_Group.end(). + + if (this_link->sndstate == SRT_GST_IDLE) + this_link->sndstate = SRT_GST_RUNNING; + + if (this_link->sndstate == SRT_GST_RUNNING) + { + // Found you, buddy. Go on. + break; + } + + if (this_link == state.ilink) + { + // No more links. Sorry. + return m_Group.end(); + } + + // Check maybe next link... + } + +ReportLink: + + // When a link is used for sending, the load factor is + // increased by this link's unit load, which is calculated + // basing on how big share among all flight sizes this link has. + // The larger the flight window, the bigger the unit load. + // This unit load then defines how much "it costs" to send + // a packet over that link. The bigger this value is then, + // the less often will this link be selected among others. + + this_link->load_factor += this_link->unit_load; + + HLOGC(gslog.Debug, log << "linkSelect_window: link #" << distance(m_Group.begin(), this_link) + << " selected, upd load_factor=" << this_link->load_factor); + return this_link; +} +#endif + + #if ENABLE_HEAVY_LOGGING // [[using maybe_locked(CUDT::uglobal()->m_GlobControlLock)]] void CUDTGroup::debugGroup() diff --git a/srtcore/group.h b/srtcore/group.h index e0ff010ca..d06fd8b83 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -716,6 +716,8 @@ class CUDTGroup int status; // 0 = normal first entry; -1 = repeated selection int errorcode; }; + + int configure(const char* str); typedef gli_t selectLink_cb(void*, const BalancingLinkState&); CallbackHolder m_cbSelectLink; diff --git a/srtcore/group_common.cpp b/srtcore/group_common.cpp index 536bdf52c..2176f7742 100644 --- a/srtcore/group_common.cpp +++ b/srtcore/group_common.cpp @@ -53,6 +53,8 @@ SocketData prepareSocketData(CUDTSocket* s) false, false, false, + 0, // load_factor + 0, // unit_load 0, // weight 0 // pktSndDropTotal }; diff --git a/srtcore/group_common.h b/srtcore/group_common.h index d780d0b9a..be4773e6a 100644 --- a/srtcore/group_common.h +++ b/srtcore/group_common.h @@ -44,6 +44,10 @@ namespace groups bool ready_write; bool ready_error; + // Balancing data + double load_factor; + double unit_load; + // Configuration uint16_t weight; diff --git a/srtcore/srt.h b/srtcore/srt.h index b83b53c87..ece7d334b 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -242,6 +242,7 @@ typedef enum SRT_SOCKOPT { SRTO_GROUPTYPE, // Group type to which an accepted socket is about to be added, available in the handshake (ENABLE_BONDING) SRTO_PACKETFILTER = 60, // Add and configure a packet filter SRTO_RETRANSMITALGO = 61, // An option to select packet retransmission algorithm + SRTO_GROUPCONFIG, SRTO_E_SIZE // Always last element, not a valid option. } SRT_SOCKOPT; From 29735ff27abf2a11be568eb6d82f14d6e096d16e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 19 Aug 2022 16:40:21 +0200 Subject: [PATCH 03/62] First version compiled with new recv buffer. Nothing tested. --- srtcore/api.cpp | 17 +- srtcore/buffer.cpp | 15 +- srtcore/buffer.h | 14 +- srtcore/buffer_rcv.cpp | 457 ++++++++++++++++----- srtcore/buffer_rcv.h | 289 +++++++++++-- srtcore/core.cpp | 560 +++++++++++++++++-------- srtcore/core.h | 7 +- srtcore/filelist.maf | 4 +- srtcore/group.cpp | 863 ++++++++++++++++++++++++++++++++++----- srtcore/group.h | 92 ++++- srtcore/group_common.cpp | 6 +- srtcore/group_common.h | 33 +- srtcore/queue.cpp | 1 + srtcore/queue.h | 2 +- srtcore/socketconfig.cpp | 33 +- srtcore/socketconfig.h | 3 + srtcore/srt_attr_defs.h | 13 +- srtcore/sync.h | 72 +++- test/test_buffer.cpp | 7 +- 19 files changed, 1995 insertions(+), 493 deletions(-) diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 9a07be357..635129102 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -298,7 +298,7 @@ int srt::CUDTUnited::cleanup() // after which the m_bClosing flag is cheched, which // is set here above. Worst case secenario, this // pthread_join() call will block for 1 second. - CSync::signal_relaxed(m_GCStopCond); + CSync::notify_one_relaxed(m_GCStopCond); m_GCThread.join(); m_bGCStatus = false; @@ -1404,7 +1404,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // Do it after setting all stored options, as some of them may // influence some group data. - srt::groups::SocketData data = srt::groups::prepareSocketData(ns); + srt::groups::SocketData data = srt::groups::prepareSocketData(ns, g.type()); if (targets[tii].token != -1) { // Reuse the token, if specified by the caller @@ -1479,14 +1479,6 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i int isn = g.currentSchedSequence(); - // Don't synchronize ISN in case of synch on msgno. Every link - // may send their own payloads independently. - if (g.synconmsgno()) - { - HLOGC(aclog.Debug, log << "groupConnect: NOT synchronizing sequence numbers: will sync on msgno"); - isn = -1; - } - // Set it the groupconnect option, as all in-group sockets should have. ns->core().m_config.iGroupConnect = 1; @@ -2596,10 +2588,11 @@ void srt::CUDTUnited::checkBrokenSockets() // this function is called (isRcvDataReady also checks if the // available data is "ready to play"). #if ENABLE_NEW_RCVBUFFER - && s->core().m_pRcvBuffer->hasAvailablePackets()) + && s->core().m_pRcvBuffer->hasAvailablePackets() #else - && s->core().m_pRcvBuffer->isRcvDataAvailable()) + && s->core().m_pRcvBuffer->isRcvDataAvailable() #endif + ) { const int bc = s->core().m_iBrokenCounter.load(); if (bc > 0) diff --git a/srtcore/buffer.cpp b/srtcore/buffer.cpp index 716c883bb..e0cb6297d 100644 --- a/srtcore/buffer.cpp +++ b/srtcore/buffer.cpp @@ -228,7 +228,7 @@ CSndBuffer::~CSndBuffer() releaseMutex(m_BufLock); } -void CSndBuffer::addBuffer(const char* data, int len, void* selink, SRT_MSGCTRL& w_mctrl) +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; @@ -308,7 +308,6 @@ void CSndBuffer::addBuffer(const char* data, int len, void* selink, SRT_MSGCTRL& // [PB_FIRST] [PB_LAST] - 2 packets per message // [PB_SOLO] - 1 packet per message - s->m_pSelectedLink = selink; s->m_iTTL = ttl; s->m_tsRexmitTime = time_point(); s->m_tsOriginTime = m_tsLastOriginTime; @@ -406,7 +405,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, void* member_marker) +int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc) { int readlen = 0; w_seqnoinc = 0; @@ -457,16 +456,6 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, continue; } - if (member_marker && member_marker != m_pCurrBlock->m_pSelectedLink) - { - LOGC(bslog.Debug, log << CONID() << "CSndBuffer: skipping packet %" << p->m_iSeqNo << " #" << p->getMsgSeq() - << " intendef for link " << m_pCurrBlock->m_pSelectedLink); - // Skip this packet due to not being set up to be sent over THIS link in balancing mode. - readlen = 0; - ++w_seqnoinc; - continue; - } - HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: extracting packet size=" << readlen << " to send"); break; } diff --git a/srtcore/buffer.h b/srtcore/buffer.h index de59df768..3ac7b3e95 100644 --- a/srtcore/buffer.h +++ b/srtcore/buffer.h @@ -175,7 +175,7 @@ class CSndBuffer /// @param [in] len size of the block. /// @param [inout] w_mctrl Message control data SRT_ATTR_EXCLUDES(m_BufLock) - void addBuffer(const char* data, int len, void* link, SRT_MSGCTRL& w_mctrl); + void addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl); /// Read a block of data from file and insert it into the sending list. /// @param [in] ifs input file stream. @@ -191,7 +191,7 @@ class CSndBuffer /// @param [out] seqnoinc the number of packets skipped due to TTL, so that seqno should be incremented. /// @return Actual length of data read. SRT_ATTR_EXCLUDES(m_BufLock) - int readData(CPacket& w_packet, time_point& w_origintime, int kflgs, int& w_seqnoinc, void* member_marker); + int readData(CPacket& 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. @@ -264,18 +264,8 @@ class CSndBuffer time_point m_tsRexmitTime; // packet retransmission time int m_iTTL; // time to live (milliseconds) - void* m_pSelectedLink; - Block* m_pNext; // next block - // This is only to assign fields that will be different - // only in specific situations, so normally these fields - // will not be overwritten. - Block() - : m_pSelectedLink(NULL) - { - } - int32_t getMsgSeq() { // NOTE: this extracts message ID with regard to REXMIT flag. diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 2ee763a00..768021751 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -1,4 +1,3 @@ -#if ENABLE_NEW_RCVBUFFER #include #include #include "buffer_rcv.h" @@ -54,7 +53,7 @@ namespace { * RcvBufferNew (circular buffer): * * |<------------------- m_iSize ----------------------------->| - * | |<----------- m_iMaxPosInc ------------>| | + * | |<----------- m_iMaxPosOff ------------>| | * | | | | * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ * | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] @@ -68,20 +67,22 @@ namespace { * thread safety: * m_iStartPos: CUDT::m_RecvLock * m_iLastAckPos: CUDT::m_AckLock - * m_iMaxPosInc: none? (modified on add and ack + * m_iMaxPosOff: none? (modified on add and ack */ -CRcvBufferNew::CRcvBufferNew(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool bMessageAPI) +CRcvBufferNew::CRcvBufferNew(int initSeqNo, size_t size, /*CUnitQueue* unitqueue, */ bool bMessageAPI) : m_entries(size) , m_szSize(size) // TODO: maybe just use m_entries.size() - , m_pUnitQueue(unitqueue) - , m_iStartSeqNo(initSeqNo) + //, m_pUnitQueue(unitqueue) + , m_iStartSeqNo(initSeqNo) // NOTE: SRT_SEQNO_NONE is allowed here. , m_iStartPos(0) + , m_iEndPos(0) + , m_iDropPos(0) , m_iFirstNonreadPos(0) - , m_iMaxPosInc(0) + , m_iMaxPosOff(0) , m_iNotch(0) - , m_numOutOfOrderPackets(0) - , m_iFirstReadableOutOfOrder(-1) + , m_numRandomPackets(0) + , m_iFirstRandomMsgPos(-1) , m_bPeerRexmitFlag(true) , m_bMessageAPI(bMessageAPI) , m_iBytesCount(0) @@ -93,18 +94,18 @@ CRcvBufferNew::CRcvBufferNew(int initSeqNo, size_t size, CUnitQueue* unitqueue, CRcvBufferNew::~CRcvBufferNew() { - // Can be optimized by only iterating m_iMaxPosInc from m_iStartPos. + // Can be optimized by only iterating m_iMaxPosOff from m_iStartPos. for (FixedArray::iterator it = m_entries.begin(); it != m_entries.end(); ++it) { if (!it->pUnit) continue; - - m_pUnitQueue->makeUnitFree(it->pUnit); + + it->pUnit->m_pParentQueue->makeUnitFree(it->pUnit); it->pUnit = NULL; } } -int CRcvBufferNew::insert(CUnit* unit) +CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) { SRT_ASSERT(unit != NULL); const int32_t seqno = unit->m_Packet.getSeqNo(); @@ -118,50 +119,228 @@ int CRcvBufferNew::insert(CUnit* unit) if (offset < 0) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -2"); - return -2; + return InsertInfo(InsertInfo::BELATED); } if (offset >= (int)capacity()) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -3"); - return -3; + return InsertInfo(InsertInfo::DISCREPANCY); } // 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); - const int pos = (m_iStartPos + offset) % m_szSize; - if (offset >= m_iMaxPosInc) - m_iMaxPosInc = offset + 1; + const int newpktpos = incPos(m_iStartPos, offset); + const int prev_max_off = m_iMaxPosOff; + bool extended_end = false; + if (offset >= m_iMaxPosOff) + { + m_iMaxPosOff = offset + 1; + extended_end = true; + } // Packet already exists - SRT_ASSERT(pos >= 0 && pos < int(m_szSize)); - if (m_entries[pos].status != EntryState_Empty) + // (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). + SRT_ASSERT(newpktpos >= 0 && newpktpos < int(m_szSize)); + if (m_entries[newpktpos].status != EntryState_Empty) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -1"); - return -1; + return InsertInfo(InsertInfo::REDUNDANT); } - SRT_ASSERT(m_entries[pos].pUnit == NULL); + SRT_ASSERT(m_entries[newpktpos].pUnit == NULL); - m_pUnitQueue->makeUnitGood(unit); - m_entries[pos].pUnit = unit; - m_entries[pos].status = EntryState_Avail; + CUnitQueue* q = unit->m_pParentQueue; + q->makeUnitGood(unit); + m_entries[newpktpos].pUnit = unit; + m_entries[newpktpos].status = EntryState_Avail; countBytes(1, (int)unit->m_Packet.getLength()); + // 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; + + int prev_max_pos = incPos(m_iStartPos, prev_max_off); + + // Update flags + // Case [A] + if (extended_end) + { + // THIS means that the buffer WAS CONTIGUOUS BEFORE. + if (m_iEndPos == prev_max_pos) + { + // THIS means that the new packet didn't CAUSE a gap + if (m_iMaxPosOff == prev_max_off + 1) + { + // This means that m_iEndPos now shifts by 1, + // and m_iDropPos must be shifted together with it, + // as there's no drop to point. + m_iEndPos = incPos(m_iStartPos, m_iMaxPosOff); + m_iDropPos = m_iEndPos; + } + else + { + // Otherwise we have a drop-after-gap candidate + // which is the currently inserted packet. + // Therefore m_iEndPos STAYS WHERE IT IS. + m_iDropPos = incPos(m_iStartPos, m_iMaxPosOff - 1); + } + } + } + // + // Since this place, every newpktpos is in the range + // between m_iEndPos (inclusive) and a position for m_iMaxPosOff. + + // Here you can use prev_max_pos as the position represented + // by m_iMaxPosOff, as if !extended_end, it was unchanged. + else if (newpktpos == m_iEndPos) + { + // Case [D]: inserted a packet at the first gap following the + // contiguous region. This makes a potential to extend the + // contiguous region and we need to find its end. + + // If insertion happened at the very first packet, it is the + // new earliest packet now. In any other situation under this + // condition there's some contiguous packet range preceding + // this position. + if (m_iEndPos == m_iStartPos) + { + earlier_time = getPktTsbPdTime(unit->m_Packet.getMsgTimeStamp()); + } + + updateGapInfo(prev_max_pos); + } + // XXX Not sure if that's the best performant comparison + // What is meant here is that newpktpos is between + // m_iEndPos and m_iDropPos, though we know it's after m_iEndPos. + // CONSIDER: make m_iDropPos rather m_iDropOff, this will make + // this comparison a simple subtraction. Note that offset will + // have to be updated on every shift of m_iStartPos. + else if (cmpPos(newpktpos, m_iDropPos) < 0) + { + // Case [C]: the newly inserted packet precedes the + // previous earliest delivery position after drop, + // that is, there is now a "better" after-drop delivery + // candidate. + + // New position updated a valid packet on an earlier + // position than the drop position was before, although still + // following a gap. + // + // We know it because if the position has filled a gap following + // a valid packet, this preceding valid packet would be pointed + // by m_iDropPos, or it would point to some earlier packet in a + // contiguous series of valid packets following a gap, hence + // the above condition wouldn't be satisfied. + m_iDropPos = newpktpos; + + // If there's an inserted packet BEFORE drop-pos (which makes it + // a new drop-pos), while the very first packet is absent (the + // below condition), it means we have a new earliest-available + // packet. Otherwise we would have only a newly updated drop + // position, but still following some earlier contiguous range + // of valid packets - so it's earlier than previous drop, but + // not earlier than the earliest packet. + if (m_iStartPos == m_iEndPos) + { + earlier_time = getPktTsbPdTime(unit->m_Packet.getMsgTimeStamp()); + } + } + // OTHERWISE: case [D] in which nothing is to be updated. + // If packet "in order" flag is zero, it can be read out of order. // With TSBPD enabled packets are always assumed in order (the flag is ignored). if (!m_tsbpd.isEnabled() && m_bMessageAPI && !unit->m_Packet.getMsgOrderFlag()) { - ++m_numOutOfOrderPackets; - onInsertNotInOrderPacket(pos); + ++m_numRandomPackets; + onInsertNotInOrderPacket(newpktpos); } updateNonreadPos(); + + CPacket* avail_packet = NULL; + + if (m_entries[m_iStartPos].pUnit && m_entries[m_iStartPos].status == EntryState_Avail) + { + avail_packet = &packetAt(m_iStartPos); + } + else if (!m_tsbpd.isEnabled() && m_iFirstRandomMsgPos != -1) + { + // In case when TSBPD is off, we take into account the message mode + // where messages may potentially span for multiple packets, therefore + // the only "next deliverable" is the first complete message that satisfies + // the order requirement. + avail_packet = &packetAt(m_iFirstRandomMsgPos); + } + else if (m_iDropPos != m_iEndPos) + { + avail_packet = &packetAt(m_iDropPos); + } + IF_RCVBUF_DEBUG(scoped_log.ss << " returns 0 (OK)"); - return 0; + + if (avail_packet) + return InsertInfo(InsertInfo::INSERTED, avail_packet->getSeqNo(), earlier_time); + else + return InsertInfo(InsertInfo::INSERTED); // No packet candidate (NOTE: impossible in live mode) +} + +// This function should be called after having m_iEndPos +// has somehow be set to position of a non-empty cell. +// This can happen by two reasons: +// - the cell has been filled by incoming packet +// - the value has been reset due to shifted m_iStartPos +// This means that you have to search for a new gap and +// update the m_iEndPos and m_iDropPos fields, or set them +// both to the end of range. +// +// prev_max_pos should be the position represented by m_iMaxPosOff. +// Passed because it is already calculated in insert(), otherwise +// it would have to be calculated here again. +void CRcvBufferNew::updateGapInfo(int prev_max_pos) +{ + int pos = m_iEndPos; + + // First, search for the next gap, max until m_iMaxPosOff. + for ( ; pos != prev_max_pos; pos = incPos(pos)) + { + if (m_entries[pos].status == EntryState_Empty) + { + break; + } + } + if (pos == prev_max_pos) + { + // Reached the end and found no gaps. + m_iEndPos = prev_max_pos; + m_iDropPos = prev_max_pos; + } + else + { + // Found a gap at pos + m_iEndPos = pos; + m_iDropPos = pos; // fallback, although SHOULD be impossible + // So, search for the first position to drop up to. + for ( ; pos != prev_max_pos; pos = incPos(pos)) + { + if (m_entries[pos].status != EntryState_Empty) + { + m_iDropPos = pos; + break; + } + } + } } +/// Request to remove from the receiver buffer +/// all packets with earlier sequence than @a seqno. +/// (Meaning, the packet with given sequence shall +/// be the first packet in the buffer after the operation). int CRcvBufferNew::dropUpTo(int32_t seqno) { IF_RCVBUF_DEBUG(ScopedLog scoped_log); @@ -174,9 +353,9 @@ int CRcvBufferNew::dropUpTo(int32_t seqno) return 0; } - m_iMaxPosInc -= len; - if (m_iMaxPosInc < 0) - m_iMaxPosInc = 0; + m_iMaxPosOff -= len; + if (m_iMaxPosOff < 0) + m_iMaxPosOff = 0; const int iDropCnt = len; while (len > 0) @@ -191,13 +370,24 @@ int CRcvBufferNew::dropUpTo(int32_t seqno) // Update positions m_iStartSeqNo = seqno; // Move forward if there are "read/drop" entries. + // (This call MAY shift m_iStartSeqNo further.) releaseNextFillerEntries(); + + // Check only m_iDropPos. The m_iEndPos pointer must + // point either to the same cell as m_iDropPos, or earlier. + if (cmpPos(m_iDropPos, m_iStartPos) < 0) + { + // Start from here and search fort the next gap + m_iEndPos = m_iDropPos = m_iStartSeqNo; + updateGapInfo(incPos(m_iStartPos, m_iMaxPosOff)); + } + // Set nonread position to the starting position before updating, // because start position was increased, and preceeding packets are invalid. m_iFirstNonreadPos = m_iStartPos; updateNonreadPos(); if (!m_tsbpd.isEnabled() && m_bMessageAPI) - updateFirstReadableOutOfOrder(); + updateFirstReadableRandom(); return iDropCnt; } @@ -206,7 +396,7 @@ int CRcvBufferNew::dropAll() if (empty()) return 0; - const int end_seqno = CSeqNo::incseq(m_iStartSeqNo, m_iMaxPosInc); + const int end_seqno = CSeqNo::incseq(m_iStartSeqNo, m_iMaxPosOff); return dropUpTo(end_seqno); } @@ -215,7 +405,7 @@ int CRcvBufferNew::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) IF_RCVBUF_DEBUG(ScopedLog scoped_log); IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBufferNew::dropMessage: seqnolo " << seqnolo << " seqnohi " << seqnohi << " m_iStartSeqNo " << m_iStartSeqNo); // TODO: count bytes as removed? - const int end_pos = incPos(m_iStartPos, m_iMaxPosInc); + const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); if (msgno != 0) { IF_RCVBUF_DEBUG(scoped_log.ss << " msgno " << msgno); @@ -242,6 +432,15 @@ int CRcvBufferNew::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) // Check if units before m_iFirstNonreadPos are dropped. bool needUpdateNonreadPos = (minDroppedOffset != -1 && minDroppedOffset <= getRcvDataSize()); releaseNextFillerEntries(); + + // Required update after shifted m_iStartPos + if (cmpPos(m_iDropPos, m_iStartPos) < 0) + { + // Start from here and search fort the next gap + m_iEndPos = m_iDropPos = m_iStartSeqNo; + updateGapInfo(end_pos); + } + if (needUpdateNonreadPos) { m_iFirstNonreadPos = m_iStartPos; @@ -249,9 +448,9 @@ int CRcvBufferNew::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) } if (!m_tsbpd.isEnabled() && m_bMessageAPI) { - if (!checkFirstReadableOutOfOrder()) - m_iFirstReadableOutOfOrder = -1; - updateFirstReadableOutOfOrder(); + if (!checkFirstReadableRandom()) + m_iFirstRandomMsgPos = -1; + updateFirstReadableRandom(); } return iDropCnt; } @@ -297,24 +496,26 @@ int CRcvBufferNew::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) } if (!m_tsbpd.isEnabled() && m_bMessageAPI) { - if (!checkFirstReadableOutOfOrder()) - m_iFirstReadableOutOfOrder = -1; - updateFirstReadableOutOfOrder(); + if (!checkFirstReadableRandom()) + m_iFirstRandomMsgPos = -1; + updateFirstReadableRandom(); } return iDropCnt; } -int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) +int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair* pw_seqrange) { const bool canReadInOrder = hasReadableInorderPkts(); - if (!canReadInOrder && m_iFirstReadableOutOfOrder < 0) + if (!canReadInOrder && m_iFirstRandomMsgPos < 0) { LOGC(rbuflog.Warn, log << "CRcvBufferNew.readMessage(): nothing to read. Ignored isRcvDataReady() result?"); return 0; } - const int readPos = canReadInOrder ? m_iStartPos : m_iFirstReadableOutOfOrder; + //const bool canReadInOrder = m_iFirstNonreadPos != m_iStartPos; + const int readPos = canReadInOrder ? m_iStartPos : m_iFirstRandomMsgPos; + const bool isReadingFromStart = (readPos == m_iStartPos); // Indicates if the m_iStartPos can be changed IF_RCVBUF_DEBUG(ScopedLog scoped_log); IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBufferNew::readMessage. m_iStartSeqNo " << m_iStartSeqNo << " m_iStartPos " << m_iStartPos << " readPos " << readPos); @@ -323,7 +524,10 @@ int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) char* dst = data; int pkts_read = 0; int bytes_extracted = 0; // The total number of bytes extracted from the buffer. - const bool updateStartPos = (readPos == m_iStartPos); // Indicates if the m_iStartPos can be changed + + int32_t out_seqlo = SRT_SEQNO_NONE; + int32_t out_seqhi = SRT_SEQNO_NONE; + for (int i = readPos;; i = incPos(i)) { SRT_ASSERT(m_entries[i].pUnit); @@ -337,6 +541,11 @@ int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) const size_t pktsize = packet.getLength(); const int32_t pktseqno = packet.getSeqNo(); + if (out_seqlo == SRT_SEQNO_NONE) + out_seqlo = pktseqno; + + out_seqhi = pktseqno; + // unitsize can be zero const size_t unitsize = std::min(remain, pktsize); memcpy(dst, packet.m_pcData, unitsize); @@ -349,8 +558,8 @@ int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) if (m_tsbpd.isEnabled()) updateTsbPdTimeBase(packet.getMsgTimeStamp()); - if (m_numOutOfOrderPackets && !packet.getMsgOrderFlag()) - --m_numOutOfOrderPackets; + if (m_numRandomPackets && !packet.getMsgOrderFlag()) + --m_numRandomPackets; const bool pbLast = packet.getMsgBoundary() & PB_LAST; if (msgctrl && (packet.getMsgBoundary() & PB_FIRST)) @@ -365,11 +574,26 @@ int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) msgctrl->pktseq = pktseqno; releaseUnitInPos(i); - if (updateStartPos) + if (isReadingFromStart) { m_iStartPos = incPos(i); - --m_iMaxPosInc; - SRT_ASSERT(m_iMaxPosInc >= 0); + --m_iMaxPosOff; + + // m_iEndPos and m_iDropPos should be + // equal to m_iStartPos only if the buffer + // is empty - but in this case the extraction will + // not be done. Otherwise m_iEndPos should + // point to the first empty cell, and m_iDropPos + // point to the first busy cell after a gap, or + // at worst be equal to m_iEndPos. + + // Therefore none of them should be updated + // because they should be constantly updated + // on an incoming packet, while this function + // should not read further than to the first + // empty cell at worst. + + SRT_ASSERT(m_iMaxPosOff >= 0); m_iStartSeqNo = CSeqNo::incseq(pktseqno); } else @@ -380,8 +604,8 @@ int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) if (pbLast) { - if (readPos == m_iFirstReadableOutOfOrder) - m_iFirstReadableOutOfOrder = -1; + if (readPos == m_iFirstRandomMsgPos) + m_iFirstRandomMsgPos = -1; break; } } @@ -390,16 +614,16 @@ int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) releaseNextFillerEntries(); - if (!isInRange(m_iStartPos, m_iMaxPosInc, m_szSize, m_iFirstNonreadPos)) + if (!isInRange(m_iStartPos, m_iMaxPosOff, m_szSize, m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; //updateNonreadPos(); } if (!m_tsbpd.isEnabled()) - // We need updateFirstReadableOutOfOrder() here even if we are reading inorder, + // We need updateFirstReadableRandom() here even if we are reading inorder, // incase readable inorder packets are all read out. - updateFirstReadableOutOfOrder(); + updateFirstReadableRandom(); const int bytes_read = int(dst - data); if (bytes_read < bytes_extracted) @@ -409,6 +633,9 @@ int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) IF_RCVBUF_DEBUG(scoped_log.ss << " pldi64 " << *reinterpret_cast(data)); + if (pw_seqrange) + *pw_seqrange = make_pair(out_seqlo, out_seqhi); + return bytes_read; } @@ -486,8 +713,8 @@ int CRcvBufferNew::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) m_iNotch = 0; m_iStartPos = p; - --m_iMaxPosInc; - SRT_ASSERT(m_iMaxPosInc >= 0); + --m_iMaxPosOff; + SRT_ASSERT(m_iMaxPosOff >= 0); m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); } else @@ -503,7 +730,7 @@ int CRcvBufferNew::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) // Update positions // Set nonread position to the starting position before updating, // because start position was increased, and preceeding packets are invalid. - if (!isInRange(m_iStartPos, m_iMaxPosInc, m_szSize, m_iFirstNonreadPos)) + if (!isInRange(m_iStartPos, m_iMaxPosOff, m_szSize, m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; } @@ -528,15 +755,12 @@ int CRcvBufferNew::readBufferToFile(fstream& ofs, int len) bool CRcvBufferNew::hasAvailablePackets() const { - return hasReadableInorderPkts() || (m_numOutOfOrderPackets > 0 && m_iFirstReadableOutOfOrder != -1); + return hasReadableInorderPkts() || (m_numRandomPackets > 0 && m_iFirstRandomMsgPos != -1); } int CRcvBufferNew::getRcvDataSize() const { - if (m_iFirstNonreadPos >= m_iStartPos) - return m_iFirstNonreadPos - m_iStartPos; - - return int(m_szSize + m_iFirstNonreadPos - m_iStartPos); + return offPos(m_iStartPos, m_iFirstNonreadPos); } int CRcvBufferNew::getTimespan_ms() const @@ -544,10 +768,10 @@ int CRcvBufferNew::getTimespan_ms() const if (!m_tsbpd.isEnabled()) return 0; - if (m_iMaxPosInc == 0) + if (m_iMaxPosOff == 0) return 0; - const int lastpos = incPos(m_iStartPos, m_iMaxPosInc - 1); + const int lastpos = incPos(m_iStartPos, m_iMaxPosOff - 1); int startpos = m_iStartPos; while (m_entries[startpos].pUnit == NULL) @@ -587,7 +811,8 @@ int CRcvBufferNew::getRcvDataSize(int& bytes, int& timespan) const CRcvBufferNew::PacketInfo CRcvBufferNew::getFirstValidPacketInfo() const { - const int end_pos = incPos(m_iStartPos, m_iMaxPosInc); + /* + const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); for (int i = m_iStartPos; i != end_pos; i = incPos(i)) { // TODO: Maybe check status? @@ -598,6 +823,19 @@ CRcvBufferNew::PacketInfo CRcvBufferNew::getFirstValidPacketInfo() const const PacketInfo info = { packet.getSeqNo(), i != m_iStartPos, getPktTsbPdTime(packet.getMsgTimeStamp()) }; return info; } + */ + // Check the state of the very first packet first + if (m_entries[m_iStartPos].status == EntryState_Avail) + { + SRT_ASSERT(m_entries[m_iStartPos].pUnit); + return (PacketInfo) { m_iStartSeqNo, false /*no gap*/, getPktTsbPdTime(packetAt(m_iStartSeqNo).getMsgTimeStamp()) }; + } + // If not, get the information from the drop + if (m_iDropPos != m_iEndPos) + { + const CPacket& pkt = packetAt(m_iDropPos); + return (PacketInfo) { pkt.getSeqNo(), true, getPktTsbPdTime(pkt.getMsgTimeStamp()) }; + } const PacketInfo info = { -1, false, time_point() }; return info; @@ -605,17 +843,10 @@ CRcvBufferNew::PacketInfo CRcvBufferNew::getFirstValidPacketInfo() const std::pair CRcvBufferNew::getAvailablePacketsRange() const { - const int seqno_last = CSeqNo::incseq(m_iStartSeqNo, (int) countReadable()); + const int seqno_last = CSeqNo::incseq(m_iStartSeqNo, offPos(m_iStartSeqNo, m_iFirstNonreadPos)); return std::pair(m_iStartSeqNo, seqno_last); } -size_t CRcvBufferNew::countReadable() const -{ - if (m_iFirstNonreadPos >= m_iStartPos) - return m_iFirstNonreadPos - m_iStartPos; - return m_szSize + m_iFirstNonreadPos - m_iStartPos; -} - bool CRcvBufferNew::isRcvDataReady(time_point time_now) const { const bool haveInorderPackets = hasReadableInorderPkts(); @@ -624,8 +855,8 @@ bool CRcvBufferNew::isRcvDataReady(time_point time_now) const if (haveInorderPackets) return true; - SRT_ASSERT((!m_bMessageAPI && m_numOutOfOrderPackets == 0) || m_bMessageAPI); - return (m_numOutOfOrderPackets > 0 && m_iFirstReadableOutOfOrder != -1); + SRT_ASSERT((!m_bMessageAPI && m_numRandomPackets == 0) || m_bMessageAPI); + return (m_numRandomPackets > 0 && m_iFirstRandomMsgPos != -1); } if (!haveInorderPackets) @@ -649,11 +880,11 @@ CRcvBufferNew::PacketInfo CRcvBufferNew::getFirstReadablePacketInfo(time_point t const PacketInfo info = {packet.getSeqNo(), false, time_point()}; return info; } - SRT_ASSERT((!m_bMessageAPI && m_numOutOfOrderPackets == 0) || m_bMessageAPI); - if (m_iFirstReadableOutOfOrder >= 0) + SRT_ASSERT((!m_bMessageAPI && m_numRandomPackets == 0) || m_bMessageAPI); + if (m_iFirstRandomMsgPos >= 0) { - SRT_ASSERT(m_numOutOfOrderPackets > 0); - const CPacket& packet = m_entries[m_iFirstReadableOutOfOrder].pUnit->m_Packet; + SRT_ASSERT(m_numRandomPackets > 0); + const CPacket& packet = m_entries[m_iFirstRandomMsgPos].pUnit->m_Packet; const PacketInfo info = {packet.getSeqNo(), true, time_point()}; return info; } @@ -685,7 +916,7 @@ void CRcvBufferNew::releaseUnitInPos(int pos) CUnit* tmp = m_entries[pos].pUnit; m_entries[pos] = Entry(); // pUnit = NULL; status = Empty if (tmp != NULL) - m_pUnitQueue->makeUnitFree(tmp); + tmp->m_pParentQueue->makeUnitFree(tmp); } bool CRcvBufferNew::dropUnitInPos(int pos) @@ -698,9 +929,9 @@ bool CRcvBufferNew::dropUnitInPos(int pos) } else if (m_bMessageAPI && !m_entries[pos].pUnit->m_Packet.getMsgOrderFlag()) { - --m_numOutOfOrderPackets; - if (pos == m_iFirstReadableOutOfOrder) - m_iFirstReadableOutOfOrder = -1; + --m_numRandomPackets; + if (pos == m_iFirstRandomMsgPos) + m_iFirstRandomMsgPos = -1; } releaseUnitInPos(pos); return true; @@ -715,24 +946,24 @@ void CRcvBufferNew::releaseNextFillerEntries() releaseUnitInPos(pos); pos = incPos(pos); m_iStartPos = pos; - --m_iMaxPosInc; - if (m_iMaxPosInc < 0) - m_iMaxPosInc = 0; + --m_iMaxPosOff; + if (m_iMaxPosOff < 0) + m_iMaxPosOff = 0; } } // TODO: Is this function complete? There are some comments left inside. void CRcvBufferNew::updateNonreadPos() { - if (m_iMaxPosInc == 0) + if (m_iMaxPosOff == 0) return; - const int end_pos = incPos(m_iStartPos, m_iMaxPosInc); // The empty position right after the last valid entry. + const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); // The empty position right after the last valid entry. int pos = m_iFirstNonreadPos; while (m_entries[pos].pUnit && m_entries[pos].status == EntryState_Avail) { - if (m_bMessageAPI && (m_entries[pos].pUnit->m_Packet.getMsgBoundary() & PB_FIRST) == 0) + if (m_bMessageAPI && (packetAt(pos).getMsgBoundary() & PB_FIRST) == 0) break; for (int i = pos; i != end_pos; i = incPos(i)) @@ -742,8 +973,12 @@ void CRcvBufferNew::updateNonreadPos() break; } + // m_iFirstNonreadPos is moved to the first position BEHIND + // the PB_LAST packet of the message. There's no guaratnee that + // the cell at this position isn't empty. + // Check PB_LAST only in message mode. - if (!m_bMessageAPI || m_entries[i].pUnit->m_Packet.getMsgBoundary() & PB_LAST) + if (!m_bMessageAPI || packetAt(i).getMsgBoundary() & PB_LAST) { m_iFirstNonreadPos = incPos(i); break; @@ -774,7 +1009,7 @@ int CRcvBufferNew::findLastMessagePkt() void CRcvBufferNew::onInsertNotInOrderPacket(int insertPos) { - if (m_numOutOfOrderPackets == 0) + if (m_numRandomPackets == 0) return; // If the following condition is true, there is already a packet, @@ -783,20 +1018,20 @@ void CRcvBufferNew::onInsertNotInOrderPacket(int insertPos) // // There might happen that the packet being added precedes the previously found one. // However, it is allowed to re bead out of order, so no need to update the position. - if (m_iFirstReadableOutOfOrder >= 0) + if (m_iFirstRandomMsgPos >= 0) return; // Just a sanity check. This function is called when a new packet is added. // So the should be unacknowledged packets. - SRT_ASSERT(m_iMaxPosInc > 0); + SRT_ASSERT(m_iMaxPosOff > 0); SRT_ASSERT(m_entries[insertPos].pUnit); - const CPacket& pkt = m_entries[insertPos].pUnit->m_Packet; + const CPacket& pkt = packetAt(insertPos); const PacketBoundary boundary = pkt.getMsgBoundary(); //if ((boundary & PB_FIRST) && (boundary & PB_LAST)) //{ // // This packet can be read out of order - // m_iFirstReadableOutOfOrder = insertPos; + // m_iFirstRandomMsgPos = insertPos; // return; //} @@ -812,18 +1047,18 @@ void CRcvBufferNew::onInsertNotInOrderPacket(int insertPos) if (firstPktPos < 0) return; - m_iFirstReadableOutOfOrder = firstPktPos; + m_iFirstRandomMsgPos = firstPktPos; return; } -bool CRcvBufferNew::checkFirstReadableOutOfOrder() +bool CRcvBufferNew::checkFirstReadableRandom() { - if (m_numOutOfOrderPackets <= 0 || m_iFirstReadableOutOfOrder < 0 || m_iMaxPosInc == 0) + if (m_numRandomPackets <= 0 || m_iFirstRandomMsgPos < 0 || m_iMaxPosOff == 0) return false; - const int endPos = incPos(m_iStartPos, m_iMaxPosInc); + const int endPos = incPos(m_iStartPos, m_iMaxPosOff); int msgno = -1; - for (int pos = m_iFirstReadableOutOfOrder; pos != endPos; pos = incPos(pos)) + for (int pos = m_iFirstRandomMsgPos; pos != endPos; pos = incPos(pos)) { if (!m_entries[pos].pUnit) return false; @@ -844,20 +1079,20 @@ bool CRcvBufferNew::checkFirstReadableOutOfOrder() return false; } -void CRcvBufferNew::updateFirstReadableOutOfOrder() +void CRcvBufferNew::updateFirstReadableRandom() { - if (hasReadableInorderPkts() || m_numOutOfOrderPackets <= 0 || m_iFirstReadableOutOfOrder >= 0) + if (hasReadableInorderPkts() || m_numRandomPackets <= 0 || m_iFirstRandomMsgPos >= 0) return; - if (m_iMaxPosInc == 0) + if (m_iMaxPosOff == 0) return; // TODO: unused variable outOfOrderPktsRemain? - int outOfOrderPktsRemain = (int) m_numOutOfOrderPackets; + int outOfOrderPktsRemain = (int) m_numRandomPackets; // Search further packets to the right. // First check if there are packets to the right. - const int lastPos = (m_iStartPos + m_iMaxPosInc - 1) % m_szSize; + const int lastPos = (m_iStartPos + m_iMaxPosOff - 1) % m_szSize; int posFirst = -1; int posLast = -1; @@ -896,7 +1131,7 @@ void CRcvBufferNew::updateFirstReadableOutOfOrder() if (boundary & PB_LAST) { - m_iFirstReadableOutOfOrder = posFirst; + m_iFirstRandomMsgPos = posFirst; return; } @@ -911,7 +1146,7 @@ int CRcvBufferNew::scanNotInOrderMessageRight(const int startPos, int msgNo) con { // Search further packets to the right. // First check if there are packets to the right. - const int lastPos = (m_iStartPos + m_iMaxPosInc - 1) % m_szSize; + const int lastPos = (m_iStartPos + m_iMaxPosOff - 1) % m_szSize; if (startPos == lastPos) return -1; @@ -953,7 +1188,7 @@ int CRcvBufferNew::scanNotInOrderMessageLeft(const int startPos, int msgNo) cons if (!m_entries[pos].pUnit) return -1; - const CPacket& pkt = m_entries[pos].pUnit->m_Packet; + const CPacket& pkt = packetAt(pos); if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { @@ -1010,14 +1245,14 @@ string CRcvBufferNew::strFullnessState(int iFirstUnackSeqNo, const time_point& t ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << m_szSize; ss << " pkts. "; - if (m_tsbpd.isEnabled() && m_iMaxPosInc > 0) + if (m_tsbpd.isEnabled() && m_iMaxPosOff > 0) { const PacketInfo nextValidPkt = getFirstValidPacketInfo(); ss << "(TSBPD ready in "; if (!is_zero(nextValidPkt.tsbpd_time)) { ss << count_milliseconds(nextValidPkt.tsbpd_time - tsNow) << "ms"; - const int iLastPos = incPos(m_iStartPos, m_iMaxPosInc - 1); + const int iLastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); if (m_entries[iLastPos].pUnit) { ss << ", timespan "; @@ -1068,5 +1303,3 @@ void CRcvBufferNew::updRcvAvgDataSize(const steady_clock::time_point& now) } } // namespace srt - -#endif // ENABLE_NEW_RCVBUFFER diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 4fd3fa7a1..bf8a9c1b3 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -22,28 +22,193 @@ namespace srt { -/* - * Circular receiver buffer. - * - * |<------------------- m_szSize ---------------------------->| - * | |<------------ m_iMaxPosInc ----------->| | - * | | | | - * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ - * | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] - * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ - * | | - * | \__last pkt received - * | - * \___ m_iStartPos: first message to read - * - * m_pUnit[i]->status_: 0: free, 1: good, 2: read, 3: dropped (can be combined with read?) - * - * thread safety: - * start_pos_: CUDT::m_RecvLock - * first_unack_pos_: CUDT::m_AckLock - * max_pos_inc_: none? (modified on add and ack - * first_nonread_pos_: - */ +// +// Circular receiver buffer. +// +// |<------------------- m_szSize ---------------------------->| +// | |<------------ m_iMaxPosOff ----------->| | +// | | | | +// +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ +// | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] +// +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ +// | | | | +// | | | \__last pkt received +// | | | +// | | \___ m_iDropPos +// | | +// | \___ m_iEndPos +// | +// \___ m_iStartPos: first message to read +// +// m_pUnit[i]->status_: 0: free, 1: good, 2: read, 3: dropped (can be combined with read?) +// +// thread safety: +// start_pos_: CUDT::m_RecvLock +// first_unack_pos_: CUDT::m_AckLock +// max_pos_inc_: none? (modified on add and ack +// first_nonread_pos_: +// +// +// m_iStartPos: the first packet that should be read (might be empty) +// m_iEndPos: the end of contiguous range. Empty if == m_iStartPos +// m_iDropPos: a packet available for retrieval after a drop. If == m_iEndPos, no such packet. +// +// Operational rules: +// +// Initially: +// m_iStartPos = 0 +// m_iEndPos = 0 +// m_iDropPos = 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_iEndPos shifted by 1 +// m_iDropPos = m_iEndPos +// +// 2. Position: after a loss, newest. +// +// m_iStartPos unchanged. +// m_iEndPos unchanged. +// m_iDropPos: +// - if it was == m_iEndPos, set to this +// - 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) | +// +// 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. +// class CRcvBufferNew { @@ -51,11 +216,34 @@ class CRcvBufferNew typedef sync::steady_clock::duration duration; public: - CRcvBufferNew(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool bMessageAPI); + CRcvBufferNew(int initSeqNo, size_t size, /*CUnitQueue* unitqueue, */ bool bMessageAPI); ~CRcvBufferNew(); public: + + struct InsertInfo + { + enum Result { INSERTED = 0, REDUNDANT = -1, BELATED = -2, DISCREPANCY = -3 } result; + + // Below fields are valid only if result == INSERTED. Otherwise they have trap repro. + + int first_seq; // sequence of the first available readable packet + time_point first_time; // Time of the new, earlier packet that appeared ready, or null-time if this didn't change. + + InsertInfo(Result r, int fp_seq = SRT_SEQNO_NONE, + time_point fp_time = time_point()) + : result(r), first_seq(fp_seq), first_time(fp_time) + { + } + + InsertInfo() + : result(REDUNDANT), first_seq(SRT_SEQNO_NONE) + { + } + + }; + /// Insert a unit into the buffer. /// Similar to CRcvBuffer::addData(CUnit* unit, int offset) /// @@ -65,7 +253,8 @@ class CRcvBufferNew /// @return 0 on success, -1 if packet is already in buffer, -2 if packet is before m_iStartSeqNo. /// -3 if a packet is offset is ahead the buffer capacity. // TODO: Previously '-2' also meant 'already acknowledged'. Check usage of this value. - int insert(CUnit* unit); + InsertInfo insert(CUnit* unit); + void updateGapInfo(int prev_max_pos); /// Drop packets in the receiver buffer from the current position up to the seqno (excluding seqno). /// @param [in] seqno drop units up to this sequence number @@ -95,7 +284,7 @@ class CRcvBufferNew /// @return actual number of bytes extracted from the buffer. /// 0 if nothing to read. /// -1 on failure. - int readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl = NULL); + int readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl = NULL, std::pair* pw_seqrange = NULL); /// Read acknowledged data into a user buffer. /// @param [in, out] dst pointer to the target user buffer. @@ -181,11 +370,9 @@ class CRcvBufferNew /// @note CSeqNo::seqoff(first, second) is 0 if nothing to read. std::pair getAvailablePacketsRange() const; - size_t countReadable() const; - bool empty() const { - return (m_iMaxPosInc == 0); + return (m_iMaxPosOff == 0); } /// Return buffer capacity. @@ -197,6 +384,14 @@ class CRcvBufferNew return m_szSize - 1; } + /// 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; + } + int64_t getDrift() const { return m_tsbpd.drift(); } // TODO: make thread safe? @@ -227,6 +422,18 @@ class CRcvBufferNew inline int incPos(int pos, int inc = 1) const { return (pos + inc) % m_szSize; } inline int decPos(int pos) const { return (pos - 1) >= 0 ? (pos - 1) : int(m_szSize - 1); } inline int offPos(int pos1, int pos2) const { return (pos2 >= pos1) ? (pos2 - pos1) : int(m_szSize + pos2 - pos1); } + inline int cmpPos(int pos2, int pos1) const + { + // XXX maybe not the best implementation, but this keeps up to the rule + int off1 = pos1 >= m_iStartPos ? pos1 - m_iStartPos : pos1 + m_szSize - m_iStartPos; + int off2 = pos2 >= m_iStartPos ? pos2 - m_iStartPos : pos2 + m_szSize - m_iStartPos; + + return off2 - off1; + } + + // NOTE: Assumes that pUnit != NULL + CPacket& packetAt(int pos) { return m_entries[pos].pUnit->m_Packet; } + const CPacket& packetAt(int pos) const { return m_entries[pos].pUnit->m_Packet; } private: void countBytes(int pkts, int bytes); @@ -249,9 +456,9 @@ class CRcvBufferNew /// Scan for availability of out of order packets. void onInsertNotInOrderPacket(int insertpos); - // Check if m_iFirstReadableOutOfOrder is still readable. - bool checkFirstReadableOutOfOrder(); - void updateFirstReadableOutOfOrder(); + // Check if m_iFirstRandomMsgPos is still readable. + bool checkFirstReadableRandom(); + void updateFirstReadableRandom(); int scanNotInOrderMessageRight(int startPos, int msgNo) const; int scanNotInOrderMessageLeft(int startPos, int msgNo) const; @@ -305,20 +512,26 @@ class CRcvBufferNew //static Entry emptyEntry() { return Entry { NULL, EntryState_Empty }; } - FixedArray m_entries; + 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 + //CUnitQueue* m_pUnitQueue; // the shared unit queue int m_iStartSeqNo; int m_iStartPos; // the head position for I/O (inclusive) + int m_iEndPos; // past-the-end of the contiguous region since m_iStartPos + int m_iDropPos; // points past m_iEndPos to the first deliverable after a gap, or == m_iEndPos if no such packet int m_iFirstNonreadPos; // First position that can't be read (<= m_iLastAckPos) - int m_iMaxPosInc; // the furthest data position - int m_iNotch; // the starting read point of the first unit + int m_iMaxPosOff; // the furthest data position + int m_iNotch; // index of the first byte to read in the first ready-to-read packet (used in file/stream mode) + + size_t m_numRandomPackets; // The number of stored packets with "inorder" flag set to false - size_t m_numOutOfOrderPackets; // The number of stored packets with "inorder" flag set to false - int m_iFirstReadableOutOfOrder; // In case of out ouf order packet, points to a position of the first such packet to - // read + /// Points to the first packet of a message that has out-of-order flag + /// and is complete (all packets from first to last are in the buffer). + /// If there is no such message in the buffer, it contains -1. + int m_iFirstRandomMsgPos; bool m_bPeerRexmitFlag; // Needed to read message number correctly const bool m_bMessageAPI; // Operation mode flag: message or stream. diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 2e6e78078..9061b1db9 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -291,7 +291,7 @@ void srt::CUDT::construct() m_bPeerTsbPd = false; m_iPeerTsbPdDelay_ms = 0; m_bTsbPd = false; - m_bTsbPdAckWakeup = false; + m_bWakeOnRecv = false; m_bGroupTsbPd = false; m_bPeerTLPktDrop = false; @@ -1286,8 +1286,8 @@ size_t srt::CUDT::fillHsExtGroup(uint32_t* pcmdspec) // Note: if agent is a listener, and the current version supports // both sync methods, this flag might have been changed according to // the wish of the caller. - if (m_parent->m_GroupOf->synconmsgno()) - flags |= SRT_GFLAG_SYNCONMSG; + // if (m_parent->m_GroupOf->synconmsgno()) + // flags |= SRT_GFLAG_SYNCONMSG; // NOTE: this code remains as is for historical reasons. // The initial implementation stated that the peer id be @@ -3086,10 +3086,10 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A pg->set_peerid(grpid); HLOGC(cnlog.Debug, log << "HS/RSP: group $" << pg->id() << " -> peer $" << pg->peerid() << ", copying characteristic data"); - // The call to syncWithSocket is copying + // The call to syncWithFirstSocket is copying // some interesting data from the first connected // socket. This should be only done for the first successful connection. - pg->syncWithSocket(*this, HSD_INITIATOR); + pg->syncWithFirstSocket(*this, HSD_INITIATOR); } // Otherwise the peer id must be the same as existing, otherwise // this group is considered already bound to another peer group. @@ -3223,7 +3223,7 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 if (was_empty) { - gp->syncWithSocket(s->core(), HSD_RESPONDER); + gp->syncWithFirstSocket(s->core(), HSD_RESPONDER); } } @@ -3245,7 +3245,7 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 return 0; } - s->m_GroupMemberData = gp->add(groups::prepareSocketData(s)); + s->m_GroupMemberData = gp->add(groups::prepareSocketData(s, gp->type())); s->m_GroupOf = gp; m_HSGroupType = gtp; @@ -3328,11 +3328,13 @@ void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) // with updateAfterSrtHandshake(). updateSrtSndSettings(); + /* if (gp->synconmsgno()) { HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID << ": NOT synchronizing sequence numbers."); } else + */ { // These are the values that are normally set initially by setters. int32_t snd_isn = m_iSndLastAck, rcv_isn = m_iRcvLastAck; @@ -5167,26 +5169,15 @@ void * srt::CUDT::tsbpd(void* param) THREAD_STATE_INIT("SRT:TsbPd"); -#if ENABLE_BONDING - // Make the TSBPD thread a "client" of the group, - // which will ensure that the group will not be physically - // deleted until this thread exits. - // NOTE: DO NOT LEAD TO EVER CANCEL THE THREAD!!! - CUDTUnited::GroupKeeper gkeeper(self->uglobal(), self->m_parent); -#endif - UniqueLock recv_lock(self->m_RecvLock); CSync recvdata_cc(self->m_RecvDataCond, recv_lock); CSync tsbpd_cc(self->m_RcvTsbPdCond, recv_lock); - self->m_bTsbPdAckWakeup = true; + self->m_bWakeOnRecv = true; while (!self->m_bClosing) { steady_clock::time_point tsNextDelivery; // Next packet delivery time bool rxready = false; -#if ENABLE_BONDING - bool shall_update_group = false; -#endif enterCS(self->m_RcvBufferLock); const steady_clock::time_point tnow = steady_clock::now(); @@ -5207,9 +5198,6 @@ void * srt::CUDT::tsbpd(void* param) if (info.seq_gap) { const int iDropCnt SRT_ATR_UNUSED = self->rcvDropTooLateUpTo(info.seqno); -#if ENABLE_BONDING - shall_update_group = true; -#endif #if ENABLE_LOGGING const int64_t timediff_us = count_microseconds(tnow - info.tsbpd_time); @@ -5246,48 +5234,6 @@ void * srt::CUDT::tsbpd(void* param) * Set EPOLL_IN to wakeup any thread waiting on epoll */ self->uglobal().m_EPoll.update_events(self->m_SocketID, self->m_sPollID, SRT_EPOLL_IN, true); -#if ENABLE_BONDING - // If this is NULL, it means: - // - the socket never was a group member - // - the socket was a group member, but: - // - was just removed as a part of closure - // - and will never be member of the group anymore - - // If this is not NULL, it means: - // - This socket is currently member of the group - // - This socket WAS a member of the group, though possibly removed from it already, BUT: - // - the group that this socket IS OR WAS member of is in the GroupKeeper - // - the GroupKeeper prevents the group from being deleted - // - it is then completely safe to access the group here, - // EVEN IF THE SOCKET THAT WAS ITS MEMBER IS BEING DELETED. - - // It is ensured that the group object exists here because GroupKeeper - // keeps it busy, even if you just closed the socket, remove it as a member - // or even the group is empty and was explicitly closed. - if (gkeeper.group) - { - // Functions called below will lock m_GroupLock, which in hierarchy - // lies after m_RecvLock. Must unlock m_RecvLock to be able to lock - // m_GroupLock inside the calls. - InvertedLock unrecv(self->m_RecvLock); - // The current "APP reader" needs to simply decide as to whether - // the next CUDTGroup::recv() call should return with no blocking or not. - // When the group is read-ready, it should update its pollers as it sees fit. - - // NOTE: this call will set lock to m_IncludedGroup->m_GroupLock - HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: GROUP: checking if %" << info.seqno << " makes group readable"); - gkeeper.group->updateReadState(self->m_SocketID, info.seqno); - - if (shall_update_group) - { - // A group may need to update the parallelly used idle links, - // should it have any. Pass the current socket position in order - // to skip it from the group loop. - // NOTE: SELF LOCKING. - gkeeper.group->updateLatestRcv(self->m_parent); - } - } -#endif CGlobEvent::triggerEvent(); tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } @@ -5299,7 +5245,7 @@ void * srt::CUDT::tsbpd(void* param) * Buffer at head of queue is not ready to play. * Schedule wakeup when it will be. */ - self->m_bTsbPdAckWakeup = false; + self->m_bWakeOnRecv = false; HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << info.seqno << " T=" << FormatTime(tsNextDelivery) << " - waiting " << count_milliseconds(timediff) << "ms"); @@ -5321,7 +5267,7 @@ void * srt::CUDT::tsbpd(void* param) * - Closing the connection */ HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: no data, scheduling wakeup at ack"); - self->m_bTsbPdAckWakeup = true; + self->m_bWakeOnRecv = true; THREAD_PAUSED(); tsbpd_cc.wait(); THREAD_RESUMED(); @@ -5381,7 +5327,7 @@ void * srt::CUDT::tsbpd(void *param) CSync recvdata_cc (self->m_RecvDataCond, recv_lock); CSync tsbpd_cc (self->m_RcvTsbPdCond, recv_lock); - self->m_bTsbPdAckWakeup = true; + self->m_bWakeOnRecv = true; while (!self->m_bClosing) { int32_t current_pkt_seq = 0; @@ -5545,7 +5491,7 @@ void * srt::CUDT::tsbpd(void *param) * Buffer at head of queue is not ready to play. * Schedule wakeup when it will be. */ - self->m_bTsbPdAckWakeup = false; + self->m_bWakeOnRecv = false; HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << current_pkt_seq << " T=" << FormatTime(tsbpdtime) << " - waiting " << count_milliseconds(timediff) << "ms"); @@ -5567,7 +5513,7 @@ void * srt::CUDT::tsbpd(void *param) * - Closing the connection */ HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: no data, scheduling wakeup at ack"); - self->m_bTsbPdAckWakeup = true; + self->m_bWakeOnRecv = true; THREAD_PAUSED(); tsbpd_cc.wait(); THREAD_RESUMED(); @@ -5654,14 +5600,25 @@ bool srt::CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd try { m_pSndBuffer = new CSndBuffer(32, m_iMaxSRTPayloadSize); -#if ENABLE_NEW_RCVBUFFER - SRT_ASSERT(m_iISN != -1); - m_pRcvBuffer = new srt::CRcvBufferNew(m_iISN, m_config.iRcvBufSize, m_pRcvQueue->m_pUnitQueue, m_config.bMessageAPI); + // after introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice space. + m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2); + +#if ENABLE_NEW_BONDING + // Keep the per-socket receiver buffer and receiver loss list empty. + // Reception will be redirected to the group directly. + if (!m_parent->m_GroupOf) + { + SRT_ASSERT(m_iISN != SRT_SEQNO_NONE); + m_pRcvBuffer = new srt::CRcvBufferNew(m_iISN, m_config.iRcvBufSize, /*m_pRcvQueue->m_pUnitQueue, */ m_config.bMessageAPI); + } #else + #if ENABLE_NEW_RCVBUFFER + SRT_ASSERT(m_iISN != SRT_SEQNO_NONE); + m_pRcvBuffer = new srt::CRcvBufferNew(m_iISN, m_config.iRcvBufSize, /*m_pRcvQueue->m_pUnitQueue, */ m_config.bMessageAPI); + #else m_pRcvBuffer = new CRcvBuffer(m_pRcvQueue->m_pUnitQueue, m_config.iRcvBufSize); + #endif #endif - // after introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice space. - m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2); m_pRcvLossList = new CRcvLossList(m_config.iFlightFlagSize); } catch (...) @@ -6478,18 +6435,13 @@ int srt::CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, int64 mctrl.msgttl = msttl; mctrl.inorder = inorder; mctrl.srctime = srctime; - return this->sendMessageInternal(data, len, NULL, (mctrl)); -} - -int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) -{ - return sendMessageInternal(data, len, NULL, (w_mctrl)); + return this->sendmsg2(data, len, (mctrl)); } // [[using maybe_locked(CUDTGroup::m_GroupLock, m_parent->m_GroupOf != NULL)]] // GroupLock is applied when this function is called from inside CUDTGroup::send, // which is the only case when the m_parent->m_GroupOf is not NULL. -int srt::CUDT::sendMessageInternal(const char *data, int len, void* selink, SRT_MSGCTRL& w_mctrl) +int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) { // throw an exception if not connected if (m_bBroken || m_bClosing) @@ -6698,15 +6650,28 @@ int srt::CUDT::sendMessageInternal(const char *data, int len, void* selink, SRT_ // NOTE: it's assumed that if this is a group member, then // an attempt to call srt_sendmsg2 has been rejected, and so // the pktseq field has been set by the internal group sender function. - if (m_parent->m_GroupOf - && w_mctrl.pktseq != SRT_SEQNO_NONE - && m_iSndNextSeqNo != SRT_SEQNO_NONE) + if (m_parent->m_GroupOf) { - if (CSeqNo::seqcmp(w_mctrl.pktseq, seqno) < 0) + if ( w_mctrl.pktseq != SRT_SEQNO_NONE + && m_iSndNextSeqNo != SRT_SEQNO_NONE) + { + if (CSeqNo::seqcmp(w_mctrl.pktseq, seqno) < 0) + { + HLOGC(aslog.Debug, log << CONID() << "sock:SENDING (NOT): group-req %" << w_mctrl.pktseq + << " OLDER THAN next expected %" << seqno << " - FAKE-SENDING."); + return size; + } + } + + // This synchronizes the fact of adding a new packet to the sender buffer. + // For groups that use active scheduling this will add the packet to the + // schedule, for all others it does nothing. + if (m_parent->m_GroupMemberData->use_send_schedule) { - HLOGC(aslog.Debug, log << CONID() << "sock:SENDING (NOT): group-req %" << w_mctrl.pktseq - << " OLDER THAN next expected %" << seqno << " - FAKE-SENDING."); - return size; + if (!m_parent->m_GroupOf->updateSendPacketUnique(seqno)) + { + throw CUDTException(MJ_CONNECTION, MN_CONNLOST); + } } } #endif @@ -6743,7 +6708,7 @@ int srt::CUDT::sendMessageInternal(const char *data, int len, void* selink, SRT_ // - OUTPUT: value of the sequence number to be put on the first packet at the next sendmsg2 call. // We need to supply to the output the value that was STAMPED ON THE PACKET, // which is seqno. In the output we'll get the next sequence number. - m_pSndBuffer->addBuffer(data, size, selink, (w_mctrl)); + m_pSndBuffer->addBuffer(data, size, (w_mctrl)); m_iSndNextSeqNo = w_mctrl.pktseq; w_mctrl.pktseq = seqno; @@ -7752,8 +7717,9 @@ void srt::CUDT::ackDataUpTo(int32_t ack) #endif } -#if ENABLE_BONDING && ENABLE_NEW_RCVBUFFER -void srt::CUDT::dropToGroupRecvBase() { +#if 0 // ENABLE_BONDING && ENABLE_NEW_RCVBUFFER +void srt::CUDT::dropToGroupRecvBase() +{ int32_t group_recv_base = SRT_SEQNO_NONE; if (m_parent->m_GroupOf) { @@ -7979,7 +7945,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) string reason = "first lost"; // just for "a reason" of giving particular % for ACK #endif -#if ENABLE_BONDING && ENABLE_NEW_RCVBUFFER +#if 0 // ENABLE_BONDING && ENABLE_NEW_RCVBUFFER dropToGroupRecvBase(); #endif @@ -8026,12 +7992,8 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { ackDataUpTo(ack); -#if ENABLE_BONDING -#if ENABLE_NEW_RCVBUFFER - const int32_t group_read_seq = m_pRcvBuffer->getFirstReadablePacketInfo(steady_clock::now()).seqno; -#else +#if ENABLE_OLD_BONDING const int32_t group_read_seq = CSeqNo::decseq(ack); -#endif #endif InvertedLock un_bufflock (m_RcvBufferLock); @@ -8079,18 +8041,26 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) if (m_bTsbPd) { - /* Newly acknowledged data, signal TsbPD thread */ + /* + There's no need to update TSBPD in the wake-on-recv state + from ACK because it is being done already in the receiver thread + when a newly inserted packet caused provision of a new candidate + that could be delivered soon. Also, this flag is only used in TSBPD + mode and can be only set to true in the TSBPD thread. + */ +#if !ENABLE_NEW_RCVBUFFER + // Newly acknowledged data, signal TsbPD thread // UniqueLock rcvlock(m_RecvLock); CSync tscond(m_RcvTsbPdCond, rcvlock); - // m_bTsbPdAckWakeup is protected by m_RecvLock in the tsbpd() thread - if (m_bTsbPdAckWakeup) + // m_bWakeOnRecv is protected by m_RecvLock in the tsbpd() thread + if (m_bWakeOnRecv) tscond.signal_locked(rcvlock); +#endif } else { { - UniqueLock rdlock (m_RecvLock); - CSync rdcond (m_RecvDataCond, rdlock); + CUniqueSync rdcond (m_RecvLock, m_RecvDataCond); #if ENABLE_NEW_RCVBUFFER // Locks m_RcvBufferLock, which is unlocked above by InvertedLock un_bufflock. @@ -8101,7 +8071,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) if (m_config.bSynRecving) { // signal a waiting "recv" call if there is any data available - rdcond.signal_locked(rdlock); + rdcond.notify_one(); } // acknowledge any waiting epolls to read // fix SRT_EPOLL_IN event loss but rcvbuffer still have data: @@ -8117,7 +8087,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true); } } -#if ENABLE_BONDING +#if ENABLE_OLD_BONDING if (group_read_seq != SRT_SEQNO_NONE && m_parent->m_GroupOf) { // See above explanation for double-checking @@ -8673,6 +8643,11 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { ScopedLock ack_lock(m_RecvAckLock); +#if ENABLE_NEW_BONDING + typedef vector< pair > losses_t; + losses_t losses; +#endif + // decode loss list message and insert loss into the sender loss list for (int i = 0, n = (int)(ctrlpkt.getLength() / 4); i < n; ++i) { @@ -8708,6 +8683,9 @@ 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); +#if ENABLE_NEW_BONDING + losses.push_back(make_pair(losslist_lo, losslist_hi)); +#endif } // ELSE IF losslist_hi %>= m_iSndLastAck else if (CSeqNo::seqcmp(losslist_hi, m_iSndLastAck) >= 0) @@ -8721,6 +8699,9 @@ void srt::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); +#if ENABLE_NEW_BONDING + losses.push_back(make_pair(m_iSndLastAck.load(), losslist_hi)); +#endif } else { @@ -8770,12 +8751,30 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "rcv LOSSREPORT: %" << losslist[i] << " (1 packet)"); const int num = m_pSndLossList->insert(losslist[i], losslist[i]); +#if ENABLE_NEW_BONDING + losses.push_back(make_pair(losslist[i], losslist[i])); +#endif enterCS(m_StatsLock); m_stats.sndr.lost.count(num); leaveCS(m_StatsLock); } } +#if ENABLE_NEW_BONDING + if (m_parent->m_GroupMemberData) + { + groups::SocketData* d = m_parent->m_GroupMemberData; + + if (d->use_send_schedule) + { + if (!m_parent->m_GroupOf->updateSendPacketLoss(losses)) + { + LOGC(inlog.Error, log << CONID() << "IPE: can't select link to send the loss, all broken???"); + } + } + } +#endif + } updateCC(TEV_LOSSREPORT, EventVariant(losslist, losslist_len)); @@ -9456,42 +9455,134 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime if (!m_bOpened) return false; - payload = isRetransmissionAllowed(enter_time) - ? packLostData((w_packet), (origintime)) - : 0; - - if (payload > 0) +#if ENABLE_NEW_BONDING + if (m_parent->m_GroupMemberData && m_parent->m_GroupMemberData->use_send_schedule) { - reason = "reXmit"; - } - else if (m_PacketFilter && - m_PacketFilter.packControlPacket(m_iSndCurrSeqNo, m_pCryptoControl->getSndCryptoFlags(), (w_packet))) - { - HLOGC(qslog.Debug, log << "filter: filter/CTL packet ready - packing instead of data."); - payload = (int) w_packet.getLength(); - reason = "filter"; - filter_ctl_pkt = true; // Mark that this packet ALREADY HAS timestamp field and it should not be set + // If this socket is a group member of a group that uses send scheduler, + // do not extract packets from the existing resources, but instead pick up + // the sequence from the scheduler container and extract that sequence from + // the sender buffer. - // Stats - ScopedLock lg(m_StatsLock); - m_stats.sndr.sentFilterExtra.count(1); - } - else - { - if (!packUniqueData((w_packet), (origintime))) + // Both lost packet and the fresh unique packet shall be able to be extracted + // from the sender buffer. Packet filter packets should be stored in a separate + // buffer (XXX not implemented yet) and delivered in order. + vector seqs; + if (!m_parent->m_GroupOf->getSendSchedule(m_parent->m_GroupMemberData, (seqs))) + return false; + + int nremoved = 0; + for (size_t i = 0; i < seqs.size(); ++i) + { + groups::SchedSeq ss = seqs[i]; + w_packet.m_iSeqNo = ss.seq; + if (ss.type == groups::SQT_FRESH) + new_packet_packed = true; + + nremoved = i+1; + + int msglen; + if (ss.type == groups::SQT_PFILTER) + { + // XXX packet filter extraction currently not implemented, do not use. + continue; + //filter_ctl_pkt = true; + } + else + { + const int offset = CSeqNo::seqoff(m_iSndLastDataAck, w_packet.m_iSeqNo); + if (offset < 0) + { + // Already removed from the buffer, cannot send. + HLOGC(qslog.Debug, log << "sched seq %" << w_packet.m_iSeqNo << " past the buffer"); + continue; + } + + // XXX If the sender buffer is made common for the whole group, this should + // simply extract a packet from the common sender buffer. + + payload = m_pSndBuffer->readData(offset, (w_packet), (origintime), (msglen)); + + uint32_t msgno_field = w_packet.m_iMsgNo; + + if (ss.type == groups::SQT_LOSS) + { + msgno_field |= MSGNO_REXMIT::mask; + } + else + { + msgno_field &= ~MSGNO_REXMIT::mask; + } + w_packet.m_iMsgNo = msgno_field; + + } + if (payload == -1) + { + // To be dropped, ignore. + // XXX likely here 'msglen' should be used to send drop request to the other side. + payload = 0; + continue; + } + else if (payload == 0) + { + continue; + } + } + + // Note: the sending schedule could have been updated in the meantime, but not + // by removing elements - only this function can remove elements from there, or + // when closing a socket. So this will always cover these sequences that have been + // extracted here above. + m_parent->m_GroupOf->discardSendSchedule(m_parent->m_GroupMemberData, nremoved); + + if (!payload) { + // XXX consider making that common for payload = 0 m_tsNextSendTime = steady_clock::time_point(); m_tdSendTimeDiff = steady_clock::duration::zero(); return false; } - new_packet_packed = true; + } + else +#endif + { + + payload = isRetransmissionAllowed(enter_time) + ? packLostData((w_packet), (origintime)) + : 0; + + if (payload > 0) + { + reason = "reXmit"; + } + else if (m_PacketFilter && + m_PacketFilter.packControlPacket(m_iSndCurrSeqNo, m_pCryptoControl->getSndCryptoFlags(), (w_packet))) + { + HLOGC(qslog.Debug, log << "filter: filter/CTL packet ready - packing instead of data."); + payload = (int) w_packet.getLength(); + reason = "filter"; + filter_ctl_pkt = true; // Mark that this packet ALREADY HAS timestamp field and it should not be set + + // Stats + ScopedLock lg(m_StatsLock); + m_stats.sndr.sentFilterExtra.count(1); + } + else + { + if (!packUniqueData((w_packet), (origintime))) + { + m_tsNextSendTime = steady_clock::time_point(); + m_tdSendTimeDiff = steady_clock::duration::zero(); + return false; + } + new_packet_packed = true; - // every 16 (0xF) packets, a packet pair is sent - if ((w_packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0) - probe = true; + // every 16 (0xF) packets, a packet pair is sent + if ((w_packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0) + probe = true; - payload = (int) w_packet.getLength(); - reason = "normal"; + payload = (int) w_packet.getLength(); + reason = "normal"; + } } // Normally packet.m_iTimeStamp field is set exactly here, @@ -9601,7 +9692,7 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime w_nexttime = m_tsNextSendTime; - return payload >= 0; + return payload >= 0; // XXX shouldn't be > 0 ? == 0 is only when buffer range exceeded. } bool srt::CUDT::packUniqueData(CPacket& w_packet, time_point& w_origintime) @@ -9623,17 +9714,7 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet, time_point& w_origintime) // isn't a useless redundant state copy. If it is, then taking the flags here can be removed. const int kflg = m_pCryptoControl->getSndCryptoFlags(); int pktskipseqno = 0; - void* link_marker = 0; -#if ENABLE_BONDING - if (m_parent->m_GroupOf) - { - // This marker is required for a decision whether the packet - // stored in the buffer is intended to be sent over this very link. - // Otherwise it will be skipped. - link_marker = m_parent->m_GroupMemberData; - } -#endif - const int pld_size = m_pSndBuffer->readData((w_packet), (w_origintime), kflg, (pktskipseqno), link_marker); + const int pld_size = m_pSndBuffer->readData((w_packet), (w_origintime), kflg, (pktskipseqno)); if (pktskipseqno) { // Some packets were skipped due to TTL expiry. @@ -9859,20 +9940,24 @@ bool srt::CUDT::overrideSndSeqNo(int32_t seq) return true; } -int srt::CUDT::processData(CUnit* in_unit) +int srt::CUDT::checkLazySpawnLatencyThread() { - if (m_bClosing) - return -1; - - CPacket &packet = in_unit->m_Packet; +#if ENABLE_NEW_BONDING + const bool need_tsbpd = m_bTsbPd; + const bool need_group_tsbpd = m_bGroupTsbPd && !m_bTsbPd; - // Just heard from the peer, reset the expiration count. - m_iEXPCount = 1; - m_tsLastRspTime.store(steady_clock::now()); + // Just in case, make sure that they cannot be set + // together as one. The above statement contains a fallback + // for that case. + SRT_ASSERT(!(m_bTsbPd && m_bGroupTsbPd)); - const bool need_tsbpd = m_bTsbPd || m_bGroupTsbPd; + // Also, just in case, check if the socket is associated + // when group tsbpd is needed. + SRT_ASSERT(m_parent->m_GroupOf || !m_bGroupTsbPd); +#else + const bool need_tsbpd = m_bTsbPd; +#endif - // We are receiving data, start tsbpd thread if TsbPd is enabled if (need_tsbpd && !m_RcvTsbPdThread.joinable()) { ScopedLock lock(m_RcvTsbPdStartupLock); @@ -9899,6 +9984,35 @@ int srt::CUDT::processData(CUnit* in_unit) return -1; } +#if ENABLE_NEW_BONDING + if (need_group_tsbpd) + { + // Shipped to the group function because this will + // likely require groupwise locking. + return m_parent->m_GroupOf->checkLazySpawnLatencyThread(); + } +#endif + + return 0; +} + +int srt::CUDT::processData(CUnit* in_unit) +{ + if (m_bClosing) + return -1; + + CPacket &packet = in_unit->m_Packet; + + // Just heard from the peer, reset the expiration count. + m_iEXPCount = 1; + m_tsLastRspTime.store(steady_clock::now()); + + // We are receiving data, start tsbpd thread if TsbPd is enabled + if (-1 == checkLazySpawnLatencyThread()) + { + return -1; + } + const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2; #if ENABLE_HEAVY_LOGGING static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; @@ -10062,6 +10176,10 @@ int srt::CUDT::processData(CUnit* in_unit) } #endif + // NULL time by default + time_point next_tsbpd_avail; + bool new_inserted = false; + { // Start of offset protected section // Prevent TsbPd thread from modifying Ack position while adding data @@ -10176,11 +10294,50 @@ int srt::CUDT::processData(CUnit* in_unit) } bool adding_successful = true; + + int buffer_add_result; #if ENABLE_NEW_RCVBUFFER - if (m_pRcvBuffer->insert(u) < 0) + #if ENABLE_BONDING + + { // XXX superfluous lock, if this socket is not a group member; revise! + ScopedLock protect_group_existence (uglobal().m_GlobControlLock); + + if (m_parent->m_GroupOf) + { + // NOTE: this will lock ALSO the receiver buffer lock in the group + buffer_add_result = m_parent->m_GroupOf->addDataUnit(u); + } + else + { + CRcvBufferNew::InsertInfo info = m_pRcvBuffer->insert(u); + + // Remember this value in order to CHECK if there's a need + // to request triggering TSBPD in case when TSBPD is in the + // state of waiting forever and wants to know if there's any + // possible time to wake up known earlier than that. + + // Note that in case of the "builtin group reader" (its own + // buffer), there's no need to do it here because it has also + // its own TSBPD thread. + + if (info.result == CRcvBufferNew::InsertInfo::INSERTED) + { + // This may happen multiple times in the loop, so update only if earlier. + if (next_tsbpd_avail == time_point() || next_tsbpd_avail > info.first_time) + next_tsbpd_avail = info.first_time; + new_inserted = true; + } + buffer_add_result = int(info.result); + } + + } + #else + buffer_add_result = m_pRcvBuffer->insert(u); + #endif #else - if (m_pRcvBuffer->addData(u, offset) < 0) + buffer_add_result = m_pRcvBuffer->addData(u, offset); #endif + if (buffer_add_result < 0) { // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. // So this packet is "redundant". @@ -10238,38 +10395,59 @@ int srt::CUDT::processData(CUnit* in_unit) // Otherwise it's an error. if (adding_successful) { - // XXX move this code do CUDT::defaultPacketArrival and call it from here: + // XXX move this code do CUDT::defaultPacketArrival and call it from here??? // srt_loss_seqs = CALLBACK_CALL(m_cbPacketArrival, rpkt); HLOGC(qrlog.Debug, log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo)); - if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. + bool handled = false; + + // Lame way to make an optional variable. + bool have_loss = false; + typename loss_seqs_t::value_type this_loss; + + { + ScopedLock protect_group_existence (uglobal().m_GlobControlLock); + if (m_parent->m_GroupOf) + { + // This should take out assymmetric losses, and leave only + // valid ones. This also reports the losses to the group. + have_loss = m_parent->m_GroupOf->checkPacketArrivalLoss(rpkt, (this_loss)); + handled = true; + } + } + + if (!handled && CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. { int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); - srt_loss_seqs.push_back(make_pair(seqlo, seqhi)); - - if (initial_loss_ttl) - { - // pack loss list for (possibly belated) NAK - // The LOSSREPORT will be sent in a while. + this_loss = make_pair(seqlo, seqhi); + have_loss = true; + } - for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i) - { - m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); - } - HLOGC(qrlog.Debug, - log << "FreshLoss: added sequences: " << Printable(srt_loss_seqs) - << " tolerance: " << initial_loss_ttl); - reorder_prevent_lossreport = true; - } + if (initial_loss_ttl && have_loss) + { + // pack loss list for (possibly belated) NAK + // The LOSSREPORT will be sent in a while. + + m_FreshLoss.push_back(CRcvFreshLoss(this_loss.first, this_loss.second, initial_loss_ttl)); + HLOGC(qrlog.Debug, + log << "FreshLoss: added sequences: %(" << this_loss.first << "-" << this_loss.second + << ") tolerance: " << initial_loss_ttl); + reorder_prevent_lossreport = true; } + + if (have_loss) + srt_loss_seqs.push_back(this_loss); } // Update the current largest sequence number that has been received. // Or it is a retransmitted packet, remove it from receiver loss list. + // + // Group note: for the new group receiver the group hosts the receiver + // buffer, but the socket still maintains the losses. if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvCurrSeqNo) > 0) { m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received @@ -10316,6 +10494,22 @@ int srt::CUDT::processData(CUnit* in_unit) return -1; } + // 1. This is set to true in case when TSBPD during the last check + // has seen no packet candidate to ever deliver, hence it needs + // an update on that. Note that this is also false if TSBPD thread + // isn't running. + // 2. If next_tsbpd_avail is set, it means that in the buffer there is + // a new packet that precedes the previously earliest available packet. + // This means that if TSBPD was sleeping up to the time of this earliest + // delivery (after drop), this time we have received a packet to be delivered + // earlier than that, so we need to notify TSBPD immediately so that it + // updates this itself, not sleep until the previously set time. + if ((m_bWakeOnRecv && new_inserted) || next_tsbpd_avail != time_point()) + { + CUniqueSync tsbpd_cc(m_RecvLock, m_RcvTsbPdCond); + tsbpd_cc.notify_all(); + } + if (incoming.empty()) { // Treat as excessive. This is when a filter cumulates packets @@ -10731,6 +10925,28 @@ breakbreak:; } } +// This is necessary to be called from the group that uses common receiver buffer, +// after receiving a packet from any of the sockets. + +#if ENABLE_NEW_BONDING +// This is because receiver loss is maintained by the socket that has detected it +// and it has to be removed according to its rules. This is necessary because the +// key field used here is m_iRcvLastSkipAck, which is private, and it better stay +// this way. +void srt::CUDT::skipMemberLoss(int32_t seqno) +{ + const int seq_gap_len = CSeqNo::seqoff(m_iRcvLastSkipAck, seqno); + + // seq_gap_len can be <= 0 if a packet has been dropped by the sender. + if (seq_gap_len > 0) + { + // Remove [from,to-inclusive] + dropFromLossLists(m_iRcvLastSkipAck, CSeqNo::decseq(seqno)); + m_iRcvLastSkipAck = seqno; + } +} +#endif + void srt::CUDT::dropFromLossLists(int32_t from, int32_t to) { ScopedLock lg(m_RcvLossLock); diff --git a/srtcore/core.h b/srtcore/core.h index d34cb0124..ccf5551fa 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -667,6 +667,9 @@ class CUDT /// the receiver fresh loss list. void unlose(const CPacket& oldpacket); void dropFromLossLists(int32_t from, int32_t to); +#if ENABLE_NEW_BONDING + void skipMemberLoss(int32_t seqno); +#endif void checkSndTimers(Whether2RegenKm regen = DONT_REGEN_KM); void handshakeDone() @@ -936,7 +939,7 @@ class CUDT sync::CThread m_RcvTsbPdThread; // Rcv TsbPD Thread handle sync::Condition m_RcvTsbPdCond; // TSBPD signals if reading is ready. Use together with m_RecvLock - bool m_bTsbPdAckWakeup; // Signal TsbPd thread on Ack sent + sync::atomic m_bWakeOnRecv; // Signal TsbPd thread on Ack sent sync::Mutex m_RcvTsbPdStartupLock; // Protects TSBPD thread creating and joining CallbackHolder m_cbAcceptHook; @@ -1068,6 +1071,8 @@ class CUDT bool packData(CPacket& packet, time_point& nexttime); int processData(CUnit* unit); + + int checkLazySpawnLatencyThread(); void processClose(); /// Process the request after receiving the handshake from caller. diff --git a/srtcore/filelist.maf b/srtcore/filelist.maf index 560a0463b..4c039a055 100644 --- a/srtcore/filelist.maf +++ b/srtcore/filelist.maf @@ -3,7 +3,6 @@ SOURCES api.cpp buffer.cpp -buffer_rcv.cpp cache.cpp channel.cpp common.cpp @@ -28,6 +27,9 @@ sync.cpp tsbpd_time.cpp window.cpp +SOURCES - ENABLE_NEW_RCVBUFFER +buffer_rcv.cpp + SOURCES - ENABLE_BONDING group.cpp group_backup.cpp diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 0aea6e5e1..b22435c77 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -4,6 +4,7 @@ #include "api.h" #include "group.h" +#include "socketconfig.h" using namespace std; using namespace srt::sync; @@ -251,10 +252,11 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) : m_Global(CUDT::uglobal()) , m_GroupID(-1) , m_PeerGroupID(-1) - , m_bSyncOnMsgNo(false) , m_type(gtype) , m_listener() , m_iBusy() + , m_pSndBuffer() + //, m_pRcvBuffer() , m_iSndOldestMsgNo(SRT_MSGNO_NONE) , m_iSndAckedMsgNo(SRT_MSGNO_NONE) , m_uOPT_MinStabilityTimeout_us(1000 * CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS) @@ -269,9 +271,13 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) // in the constructor body. , m_iSndTimeOut(-1) , m_iRcvTimeOut(-1) + , m_bOPT_MessageAPI(true) // XXX currently not settable + , m_iOPT_RcvBufSize(CSrtConfig::DEF_BUFFER_SIZE) , m_tsStartTime() , m_tsRcvPeerStartTime() +#if !ENABLE_NEW_RCVBUFFER , m_RcvBaseSeqNo(SRT_SEQNO_NONE) +#endif , m_bOpened(false) , m_bConnected(false) , m_bClosing(false) @@ -299,6 +305,14 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) #endif } +void CUDTGroup::createBuffers(int32_t isn) +{ + // XXX NOT YET, but will be in use. + m_pSndBuffer = NULL; + + m_pRcvBuffer.reset(new srt::CRcvBufferNew(isn, m_iOPT_RcvBufSize, /*m_pRcvQueue->m_pUnitQueue, */ m_bOPT_MessageAPI)); +} + CUDTGroup::~CUDTGroup() { srt_epoll_release(m_RcvEID); @@ -364,11 +378,44 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) case SRTO_SNDTIMEO: m_iSndTimeOut = cast_optval(optval, optlen); - break; + break; // passthrough to socket option case SRTO_RCVTIMEO: m_iRcvTimeOut = cast_optval(optval, optlen); - break; + break; // passthrough to socket option + + case SRTO_RCVBUF: + { + // This requires to obtain the possibly set MSS and FC options. + // XXX Find some more sensible way to do it. Would be nice to + // systematize the search method and default values. + int val = cast_optval(optval, optlen); + if (val <= 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + // Search if you already have SRTO_MSS set + int mss = CSrtConfig::DEF_MSS; + vector::iterator f = + find_if(m_config.begin(), m_config.end(), ConfigItem::OfType(SRTO_MSS)); + if (f != m_config.end()) + { + f->get(mss); // worst case, it will leave it unchanged. + } + + // Search if you already have SRTO_FC set + int fc = CSrtConfig::DEF_FLIGHT_SIZE; + f = find_if(m_config.begin(), m_config.end(), ConfigItem::OfType(SRTO_FC)); + if (f != m_config.end()) + { + f->get(fc); // worst case, it will leave it unchanged. + } + + if (mss <= 0 || fc <= 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + m_iOPT_RcvBufSize = srt::RcvBufferSizeOptionToValue(val, fc, mss); + } + break; // Keep passthru. This is also required for Unit queue initial size. case SRTO_GROUPMINSTABLETIMEO: { @@ -864,7 +911,7 @@ SRT_SOCKSTATUS CUDTGroup::getStatus() } // [[using locked(m_GroupLock)]]; -void CUDTGroup::syncWithSocket(const CUDT& core, const HandshakeSide side) +void CUDTGroup::syncWithFirstSocket(const CUDT& core, const HandshakeSide side) { if (side == HSD_RESPONDER) { @@ -875,6 +922,25 @@ void CUDTGroup::syncWithSocket(const CUDT& core, const HandshakeSide side) set_currentSchedSequence(core.ISN()); } +#if ENABLE_NEW_RCVBUFFER + /* + FIX: In this implementation we need to initialize the receiver buffer. + This function is called when the first socket is added to the group, + both as the first connection on the caller side and the socket connection + that spawned this group as a mirror group on the listener side. + The receiver buffer, which will be common for the group, needs ISN, + in order to be able to recover any initially lost packets. Also, + with the newly created fresh socket and very first socket in the group, + it should be completely safe to set the ISN from the first socket, + which is the same for sending and receiving. Next sockets added to + the group may have these values derived from the group, and they can + differ in sender and receiver. + */ + + m_RcvLastSeqNo = core.ISN(); + createBuffers(core.ISN()); + +#else // XXX // Might need further investigation as to whether this isn't // wrong for some cases. By having this -1 here the value will be @@ -887,6 +953,7 @@ void CUDTGroup::syncWithSocket(const CUDT& core, const HandshakeSide side) // // Previous implementation used setting to: core.m_iPeerISN resetInitialRxSequence(); +#endif // Get the latency (possibly fixed against the opposite side) // from the first socket (core.m_iTsbPdDelay_ms), @@ -894,6 +961,114 @@ void CUDTGroup::syncWithSocket(const CUDT& core, const HandshakeSide side) set_latency(core.m_iTsbPdDelay_ms * int64_t(1000)); } +#if ENABLE_NEW_RCVBUFFER + +int CUDTGroup::addDataUnit(CUnit* u) +{ + // XXX Likely some other things need to be done here. + CRcvBufferNew::InsertInfo info; + + { + ScopedLock lk (m_RcvBufferLock); + info = m_pRcvBuffer->insert(u); + } + + if (info.result == CRcvBufferNew::InsertInfo::INSERTED) + { + // If m_bWakeOnRecv, then notify anyway. + // Otherwise notify only if a "fresher" packet was added, + // so TSBPD should interrupt its sleep earlier and re-check. + if (m_bWakeOnRecv || info.first_time != time_point()) + { + // Make a lock on data reception first, to protect the buffer. + // Then notify TSBPD if required. + CUniqueSync tsbpd_cc(m_RcvDataLock, m_RcvTsbPdCond); + tsbpd_cc.notify_all(); + } + } + + return int(info.result); +} + + +int CUDTGroup::rcvDropTooLateUpTo(int seqno) +{ + int iDropCnt = 0; + + { + ScopedLock lk (m_RcvBufferLock); + + // Nothing to drop from an empty buffer. + // Required to check first to secure size()-1 expression. + if (!m_pRcvBuffer->empty()) + { + // Make sure that it would not drop over m_iRcvCurrSeqNo, which may break senders. + int32_t last_seq = CSeqNo::incseq(m_pRcvBuffer->getStartSeqNo(), m_pRcvBuffer->size() - 1); + if (CSeqNo::seqcmp(seqno, last_seq) > 0) + seqno = last_seq; + + iDropCnt = m_pRcvBuffer->dropUpTo(seqno); + + /* not sure how to stats. + if (iDropCnt > 0) + { + enterCS(m_StatsLock); + // Estimate dropped bytes from average payload size. + const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); + m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * avgpayloadsz, (uint32_t) iDropCnt)); + leaveCS(m_StatsLock); + } + */ + } + } + + // Update every member's loss lists + { + ScopedLock lk (m_GroupLock); + + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + CUDT& u = gi->ps->core(); + u.skipMemberLoss(seqno); + } + } + + return iDropCnt; +} + + +bool CUDTGroup::checkPacketArrivalLoss(const CPacket& rpkt, typename CUDT::loss_seqs_t::value_type& w_loss) +{ + // This is called when the packet was added to the buffer and this + // adding was successful. Here we need to: + + // - check contiguity of the range between the last read and this packet + // - update the m_RcvLastSeqNo to the last packet's sequence, if this was the newest packet + + // Note that we don't need to keep the latest contiguous packet sequence + // because whatever non-contiguous range has been detected, it was notified + // in the losses. + + bool have = false; + + if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_RcvLastSeqNo) > 0)) + { + int32_t seqlo = CSeqNo::incseq(m_RcvLastSeqNo); + int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); + + w_loss = make_pair(seqlo, seqhi); + have = true; + } + + if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_RcvLastSeqNo) > 0) + { + m_RcvLastSeqNo = rpkt.m_iSeqNo; + } + + return have; +} +#endif + void CUDTGroup::close() { // Close all descriptors, then delete the group. @@ -1072,7 +1247,6 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool vector wipeme; vector idleLinks; vector pendingSockets; // need sock ids as it will be checked out of lock - map linkIndexMap; int32_t curseq = SRT_SEQNO_NONE; // The seqno of the first packet of this message. int32_t nextseq = SRT_SEQNO_NONE; // The seqno of the first packet of next message. @@ -1191,28 +1365,41 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool if (w_mc.srctime == 0) w_mc.srctime = count_microseconds(steady_clock::now().time_since_epoch()); - void* member_marker = NULL; -#if ENABLE_NEW_RCVBUFFER - - BalancingLinkState lstate = { m_Group.active(), 0, 0 }; - gli_t selink = m_Group.end(); - - if (use_select) + for (vector::iterator snd = activeLinks.begin(); snd != activeLinks.end(); ++snd) { - selink = CALLBACK_CALL(m_cbSelectLink, lstate); - if (selink == m_Group.end()) + gli_t d = *snd; + int erc = 0; // success + // Remaining sndstate is SRT_GST_RUNNING. Send a payload through it. + try + { + // This must be wrapped in try-catch because on error it throws an exception. + // Possible return values are only 0, in case when len was passed 0, or a positive + // >0 value that defines the size of the data that it has sent, that is, in case + // of Live mode, equal to 'len'. + stat = d->ps->core().sendmsg2(buf, len, (w_mc)); + } + catch (CUDTException& e) { - // If this returns the "trap" link index, it means - // that no link is qualified for sending. + cx = e; + stat = -1; + erc = e.getErrorCode(); + } - throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + if (stat != -1) + { + curseq = w_mc.pktseq; + nextseq = d->ps->core().schedSeqNo(); } - // Now, we will use 'selink' to set the member socket - // identification to the packet sent to EVERY link. - member_marker = &*selink; + const Sendstate cstate = {d->id, &*d, stat, erc}; + sendstates.push_back(cstate); + d->sndresult = stat; + d->laststatus = d->ps->getStatus(); } -#endif + + // Ok, we have attempted to send a payload over all links + // that are currently in the RUNNING state. We know that at + // least one is successful if we have non-default curseq value. // Here we need to activate all links that are found as IDLE. // Some portion of logical exclusions: @@ -1279,7 +1466,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool try { - stat = d->ps->core().sendMessageInternal(buf, len, member_marker, (w_mc)); + stat = d->ps->core().sendmsg2(buf, len, (w_mc)); } catch (CUDTException& e) { @@ -1307,45 +1494,6 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool sendstates.push_back(cstate); } - // } - - - for (vector::iterator snd = activeLinks.begin(); snd != activeLinks.end(); ++snd) - { - gli_t d = *snd; - int erc = 0; // success - // Remaining sndstate is SRT_GST_RUNNING. Send a payload through it. - try - { - // This must be wrapped in try-catch because on error it throws an exception. - // Possible return values are only 0, in case when len was passed 0, or a positive - // >0 value that defines the size of the data that it has sent, that is, in case - // of Live mode, equal to 'len'. - stat = d->ps->core().sendMessageInternal(buf, len, member_marker, (w_mc)); - } - catch (CUDTException& e) - { - cx = e; - stat = -1; - erc = e.getErrorCode(); - } - - if (stat != -1) - { - curseq = w_mc.pktseq; - nextseq = d->ps->core().schedSeqNo(); - } - - const Sendstate cstate = {d->id, &*d, stat, erc}; - sendstates.push_back(cstate); - d->sndresult = stat; - d->laststatus = d->ps->getStatus(); - } - - // Ok, we have attempted to send a payload over all links - // that are currently in the RUNNING state. We know that at - // least one is successful if we have non-default curseq value. - if (nextseq != SRT_SEQNO_NONE) { HLOGC(gslog.Debug, @@ -1353,6 +1501,8 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool m_iLastSchedSeqNo = nextseq; } + // } + // { send_CheckBrokenSockets() if (!pendingSockets.empty()) @@ -1629,7 +1779,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool // Possible return values are only 0, in case when len was passed 0, or a positive // >0 value that defines the size of the data that it has sent, that is, in case // of Live mode, equal to 'len'. - stat = d->ps->core().sendMessageInternal(buf, len, member_marker, (w_mc)); + stat = d->ps->core().sendmsg2(buf, len, (w_mc)); } catch (CUDTException& e) { @@ -2093,6 +2243,7 @@ vector CUDTGroup::recv_WaitForReadReady(const vector& return readReady; } +#if !ENABLE_NEW_RCVBUFFER void CUDTGroup::updateReadState(SRTSOCKET /* not sure if needed */, int32_t sequence) { bool ready = false; @@ -2148,6 +2299,7 @@ int32_t CUDTGroup::getRcvBaseSeqNo() ScopedLock lg(m_GroupLock); return m_RcvBaseSeqNo; } +#endif void CUDTGroup::updateWriteState() { @@ -2155,45 +2307,12 @@ void CUDTGroup::updateWriteState() m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, true); } -/// Validate iPktSeqno is in range -/// (iBaseSeqno - m_iSeqNoTH/2; iBaseSeqno + m_iSeqNoTH). -/// -/// EXPECT_EQ(isValidSeqno(125, 124), true); // behind -/// EXPECT_EQ(isValidSeqno(125, 125), true); // behind -/// EXPECT_EQ(isValidSeqno(125, 126), true); // the next in order -/// -/// EXPECT_EQ(isValidSeqno(0, 0x3FFFFFFF - 2), true); // ahead, but ok. -/// EXPECT_EQ(isValidSeqno(0, 0x3FFFFFFF - 1), false); // too far ahead. -/// EXPECT_EQ(isValidSeqno(0x3FFFFFFF + 2, 0x7FFFFFFF), false); // too far ahead. -/// EXPECT_EQ(isValidSeqno(0x3FFFFFFF + 3, 0x7FFFFFFF), true); // ahead, but ok. -/// EXPECT_EQ(isValidSeqno(0x3FFFFFFF, 0x1FFFFFFF + 2), false); // too far (behind) -/// EXPECT_EQ(isValidSeqno(0x3FFFFFFF, 0x1FFFFFFF + 3), true); // behind, but ok -/// EXPECT_EQ(isValidSeqno(0x70000000, 0x0FFFFFFF), true); // ahead, but ok -/// EXPECT_EQ(isValidSeqno(0x70000000, 0x30000000 - 2), false); // too far ahead. -/// EXPECT_EQ(isValidSeqno(0x70000000, 0x30000000 - 3), true); // ahead, but ok -/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0), true); -/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x7FFFFFFF), true); -/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000000), false); -/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000001), false); -/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000002), true); // behind by 536870910 -/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000003), true); -/// -/// @return false if @a iPktSeqno is not inside the valid range; otherwise true. -static bool isValidSeqno(int32_t iBaseSeqno, int32_t iPktSeqno) -{ - const int32_t iLenAhead = CSeqNo::seqlen(iBaseSeqno, iPktSeqno); - if (iLenAhead >= 0 && iLenAhead < CSeqNo::m_iSeqNoTH) - return true; - - const int32_t iLenBehind = CSeqNo::seqlen(iPktSeqno, iBaseSeqno); - if (iLenBehind >= 0 && iLenBehind < CSeqNo::m_iSeqNoTH / 2) - return true; - - return false; -} +#if ENABLE_NEW_RCVBUFFER -#ifdef ENABLE_NEW_RCVBUFFER -int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) +// compile-if TO BE REMOVED. Block with the old name or something. +// This function should be rewritten to use the in-group receiver buffer. +#if 0 +int CUDTGroup::recv_old(char* buf, int len, SRT_MSGCTRL& w_mc) { // First, acquire GlobControlLock to make sure all member sockets still exist enterCS(m_Global.m_GlobControlLock); @@ -2378,7 +2497,278 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } +#endif // block by if 0 + +// The REAL version for the new group receiver. +// + +int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) +{ + CUniqueSync tscond (m_RcvDataLock, m_RcvDataCond); + + /* XXX DEBUG STUFF - enable when required + char charbool[2] = {'0', '1'}; + char ptrn [] = "RECVMSG/BEGIN BROKEN 1 CONN 1 CLOSING 1 SYNCR 1 NMSG "; + int pos [] = {21, 28, 38, 46, 53}; + ptrn[pos[0]] = charbool[m_bBroken]; + ptrn[pos[1]] = charbool[m_bConnected]; + ptrn[pos[2]] = charbool[m_bClosing]; + ptrn[pos[3]] = charbool[m_config.m_bSynRecving]; + int wrtlen = sprintf(ptrn + pos[4], "%d", m_pRcvBuffer->getRcvMsgNum()); + strcpy(ptrn + pos[4] + wrtlen, "\n"); + fputs(ptrn, stderr); + // */ + + if (m_bClosing) + { + HLOGC(arlog.Debug, log << CONID() << "grp:recv: CONNECTION BROKEN - reading from recv buffer just for formality"); + + enterCS(m_RcvBufferLock); + const int res = (m_pRcvBuffer->isRcvDataReady(steady_clock::now())) + ? m_pRcvBuffer->readMessage(data, len, &w_mctrl) + : 0; + leaveCS(m_RcvBufferLock); + + w_mctrl.srctime = 0; + + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPd) + { + HLOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup"); + tscond.notify_all(); + } + else + { + HLOGP(tslog.Debug, "NOT pinging TSBPD - not set"); + } + + if (!isRcvBufferReady()) + { + // read is not available any more + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + } + + if (res == 0) + { + if (!m_bOPT_MessageAPI && !m_bOpened) + return 0; + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + else + return res; + } + + pair seqrange; + + if (!m_bSynRecving) + { + HLOGC(arlog.Debug, log << CONID() << "grp:recv: BEGIN ASYNC MODE. Going to extract payload size=" << len); + + enterCS(m_RcvBufferLock); + const int res = (m_pRcvBuffer->isRcvDataReady(steady_clock::now())) + ? m_pRcvBuffer->readMessage(data, len, (&w_mctrl), (&seqrange)) + : 0; + leaveCS(m_RcvBufferLock); + + HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (NON-BLOCKING) result=" << res); + + if (res == 0) + { + // read is not available any more + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPd) + { + HLOGP(arlog.Debug, "grp:recv: nothing to read, kicking TSBPD, return AGAIN"); + tscond.notify_all(); + } + else + { + HLOGP(arlog.Debug, "grp:recv: nothing to read, return AGAIN"); + } + + // Shut up EPoll if no more messages in non-blocking mode + CUDT::uglobal().m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + // Forced to return 0 instead of throwing exception, in case of AGAIN/READ + throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); + } + + if (!m_pRcvBuffer->isRcvDataReady(steady_clock::now())) + { + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPd) + { + HLOGP(arlog.Debug, "grp:recv: DATA READ, but nothing more - kicking TSBPD."); + tscond.notify_all(); + } + else + { + HLOGP(arlog.Debug, "grp:recv: DATA READ, but nothing more"); + } + + // Shut up EPoll if no more messages in non-blocking mode + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + + } + return res; + } + + HLOGC(arlog.Debug, log << CONID() << "grp:recv: BEGIN SYNC MODE. Going to extract payload size max=" << len); + + int res = 0; + bool timeout = false; + // Do not block forever, check connection status each 1 sec. + const steady_clock::duration recv_timeout = m_iRcvTimeOut < 0 ? seconds_from(1) : milliseconds_from(m_iRcvTimeOut); + + CSync recv_cond (m_RcvDataCond, tscond.locker()); + + do + { + if (stillConnected() && !timeout && !m_pRcvBuffer->isRcvDataReady(steady_clock::now())) + { + /* Kick TsbPd thread to schedule next wakeup (if running) */ + if (m_bTsbPd) + { + // XXX Experimental, so just inform: + // Check if the last check of isRcvDataReady has returned any "next time for a packet". + // If so, then it means that TSBPD has fallen asleep only up to this time, so waking it up + // would be "spurious". If a new packet comes ahead of the packet which's time is returned + // in tstime (as TSBPD sleeps up to then), the procedure that receives it is responsible + // of kicking TSBPD. + // bool spurious = (tstime != 0); + + HLOGC(tslog.Debug, log << CONID() << "grp:recv: KICK tsbpd"); + tscond.notify_one(); + } + + THREAD_PAUSED(); + do + { + // `wait_for(recv_timeout)` wouldn't be correct here. Waiting should be + // only until the time that is now + timeout since the first moment + // when this started, or sliced-waiting for 1 second, if timtout is + // higher than this. + const steady_clock::time_point exptime = steady_clock::now() + recv_timeout; + + HLOGC(tslog.Debug, + log << CONID() << "grp:recv: fall asleep up to TS=" << FormatTime(exptime) + << " lock=" << (&m_RcvDataLock) << " cond=" << (&m_RcvDataCond)); + + if (!recv_cond.wait_until(exptime)) + { + if (m_iRcvTimeOut >= 0) // otherwise it's "no timeout set" + timeout = true; + HLOGP(tslog.Debug, + "grp:recv: DATA COND: EXPIRED -- checking connection conditions and rolling again"); + } + else + { + HLOGP(tslog.Debug, "grp:recv: DATA COND: KICKED."); + } + } while (stillConnected() && !timeout && (!isRcvBufferReady())); + THREAD_RESUMED(); + + HLOGC(tslog.Debug, + log << CONID() << "grp:recv: lock-waiting loop exited: stillConntected=" << stillConnected() + << " timeout=" << timeout << " data-ready=" << isRcvBufferReady()); + } + + /* XXX DEBUG STUFF - enable when required + LOGC(arlog.Debug, "RECVMSG/GO-ON BROKEN " << m_bBroken << " CONN " << m_bConnected + << " CLOSING " << m_bClosing << " TMOUT " << timeout + << " NMSG " << m_pRcvBuffer->getRcvMsgNum()); + */ + + enterCS(m_RcvBufferLock); + res = m_pRcvBuffer->readMessage((data), len, &w_mctrl); + leaveCS(m_RcvBufferLock); + HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (BLOCKING) result=" << res); + + if (m_bClosing) + { + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + else if (!m_bConnected) + { + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + } + } while ((res == 0) && !timeout); + + if (!isRcvBufferReady()) + { + // Falling here means usually that res == 0 && timeout == true. + // res == 0 would repeat the above loop, unless there was also a timeout. + // timeout has interrupted the above loop, but with res > 0 this condition + // wouldn't be satisfied. + + // read is not available any more + + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPd) + { + HLOGP(tslog.Debug, "recvmsg: KICK tsbpd() (buffer empty)"); + tscond.notify_all(); + } + + // Shut up EPoll if no more messages in non-blocking mode + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + } + + // Unblock when required + // LOGC(tslog.Debug, "RECVMSG/EXIT RES " << res << " RCVTIMEOUT"); + + if ((res <= 0) && (m_iRcvTimeOut >= 0)) + { + throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); + } + + return res; +} + #else + +// XXX +// This function isn't used in the common-buffer reading procedures, +// but can be useful to reject packets that have the sequence number +// "out of the blue". + +/// Validate iPktSeqno is in range +/// (iBaseSeqno - m_iSeqNoTH/2; iBaseSeqno + m_iSeqNoTH). +/// +/// EXPECT_EQ(isValidSeqno(125, 124), true); // behind +/// EXPECT_EQ(isValidSeqno(125, 125), true); // behind +/// EXPECT_EQ(isValidSeqno(125, 126), true); // the next in order +/// +/// EXPECT_EQ(isValidSeqno(0, 0x3FFFFFFF - 2), true); // ahead, but ok. +/// EXPECT_EQ(isValidSeqno(0, 0x3FFFFFFF - 1), false); // too far ahead. +/// EXPECT_EQ(isValidSeqno(0x3FFFFFFF + 2, 0x7FFFFFFF), false); // too far ahead. +/// EXPECT_EQ(isValidSeqno(0x3FFFFFFF + 3, 0x7FFFFFFF), true); // ahead, but ok. +/// EXPECT_EQ(isValidSeqno(0x3FFFFFFF, 0x1FFFFFFF + 2), false); // too far (behind) +/// EXPECT_EQ(isValidSeqno(0x3FFFFFFF, 0x1FFFFFFF + 3), true); // behind, but ok +/// EXPECT_EQ(isValidSeqno(0x70000000, 0x0FFFFFFF), true); // ahead, but ok +/// EXPECT_EQ(isValidSeqno(0x70000000, 0x30000000 - 2), false); // too far ahead. +/// EXPECT_EQ(isValidSeqno(0x70000000, 0x30000000 - 3), true); // ahead, but ok +/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0), true); +/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x7FFFFFFF), true); +/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000000), false); +/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000001), false); +/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000002), true); // behind by 536870910 +/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000003), true); +/// +/// @return false if @a iPktSeqno is not inside the valid range; otherwise true. +static bool isValidSeqno(int32_t iBaseSeqno, int32_t iPktSeqno) +{ + const int32_t iLenAhead = CSeqNo::seqlen(iBaseSeqno, iPktSeqno); + if (iLenAhead >= 0 && iLenAhead < CSeqNo::m_iSeqNoTH) + return true; + + const int32_t iLenBehind = CSeqNo::seqlen(iPktSeqno, iBaseSeqno); + if (iLenBehind >= 0 && iLenBehind < CSeqNo::m_iSeqNoTH / 2) + return true; + + return false; +} + + // The "app reader" version of the reading function. // This reads the packets from every socket treating them as independent // and prepared to work with the application. Then packets are sorted out @@ -2915,8 +3305,8 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) } } } -#endif +// compile-if !ENABLE_NEW_RCVBUFFER // [[using locked(m_GroupLock)]] CUDTGroup::ReadPos* CUDTGroup::checkPacketAhead() { @@ -2954,6 +3344,7 @@ CUDTGroup::ReadPos* CUDTGroup::checkPacketAhead() return out; } +#endif const char* CUDTGroup::StateStr(CUDTGroup::GroupState st) { @@ -4485,7 +4876,7 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) return stat; } -#if ENABLE_NEW_RCVBUFFER +#if 0 //ENABLE_NEW_RCVBUFFER int CUDTGroup::sendBalancing_orig(const char* buf, int len, SRT_MSGCTRL& w_mc) { // Avoid stupid errors in the beginning. @@ -5456,6 +5847,274 @@ CUDTGroup::gli_t CUDTGroup::linkSelect_window(const CUDTGroup::BalancingLinkStat << " selected, upd load_factor=" << this_link->load_factor); return this_link; } + +// Update on adding a new fresh packet to the sender buffer. +bool CUDTGroup::updateSendPacketUnique(int32_t single_seq) +{ + ScopedLock guard(m_GroupLock); + + // Check first if the packet wasn't already scheduled + // If so, do nothing and return success. + for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d) + { + if (find(d->send_schedule.begin(), d->send_schedule.end(), (SchedSeq){single_seq, groups::SQT_FRESH}) != d->send_schedule.end()) + return true; // because this should be considered successful, even though didn't schedule. + } + + BalancingLinkState lstate = { m_Group.active(), 0, 0 }; + gli_t selink = CALLBACK_CALL(m_cbSelectLink, lstate); + if (selink == m_Group.end()) + { + // If this returns the "trap" link index, it means + // that no link is qualified for sending. + return false; + } + + selink->send_schedule.push_back((groups::SchedSeq){single_seq, groups::SQT_FRESH}); + m_Group.set_active(selink); + + // XXX + // This function is called when the newly scheduled packet by + // the user is called. Therefore here must be also a procedure + // to extract RIGHT NOW and schedule (possibly to a side container) + // packet-filter control packet(s). The original function that + // possibly creates such a packet should be called here, but + // there should be also a separate container for them, as they + // simply can't be referred as sequence to the sender buffer. + + return true; +} + +// Update on received loss report or request to retransmit on NAKREPORT. +bool CUDTGroup::updateSendPacketLoss(const std::vector< std::pair >& seqlist) +{ + ScopedLock guard(m_GroupLock); + + typedef std::vector< std::pair > seqlist_t; + + BalancingLinkState lstate = { m_Group.active(), 0, 0 }; + + for (seqlist_t::const_iterator seqpair = seqlist.begin(); seqpair != seqlist.end(); ++seqpair) + { + // These are loss ranges, so believe that they are in order. + pair begin_end = *seqpair; + // The seqpair in the loss list is the first and last, both including, + // except when there's only one, in which case it's twice the same value. + // Increase the end seq by one to make it the "past the end seq". + begin_end.second = CSeqNo::incseq(begin_end.second); + + for (int32_t seq = begin_end.first; seq != begin_end.second; seq = CSeqNo::incseq(seq)) + { + // Select a link to use for every sequence. + gli_t selink = CALLBACK_CALL(m_cbSelectLink, lstate); + if (selink == m_Group.end()) + { + // Interrupt all - we have no link candidates to send. + return false; + } + + selink->send_schedule.push_back((SchedSeq){seq, groups::SQT_LOSS}); + lstate.ilink = selink; + } + } + + m_Group.set_active(lstate.ilink); + return true; +} + +SRT_ATR_NODISCARD bool CUDTGroup::getSendSchedule(SocketData* d, vector& w_seqs) +{ + // This is going to provide a packet from the packet filter control buffer + // or sender buffer. + + ScopedLock glock (m_GroupLock); + + if (d->send_schedule.empty()) + return false; + + copy(d->send_schedule.begin(), d->send_schedule.end(), + back_inserter(w_seqs)); + + return true; +} + +void CUDTGroup::discardSendSchedule(SocketData* d, int ndiscard) +{ + ScopedLock glock (m_GroupLock); + if (ndiscard > int(d->send_schedule.size())) + { + // XXX report error + d->send_schedule.clear(); + } + else if (ndiscard == int(d->send_schedule.size())) + { + d->send_schedule.clear(); + } + else + { + d->send_schedule.erase(d->send_schedule.begin(), d->send_schedule.begin() + ndiscard); + } +} + +// Receiver part + +int CUDTGroup::checkLazySpawnLatencyThread() +{ + // It is confirmed that the TSBPD thread is required, + // so just check if it's running already. + + if (!m_RcvTsbPdThread.joinable()) + { + ScopedLock lock(m_GroupLock); + + if (m_bClosing) // Check again to protect join() in CUDT::releaseSync() + return -1; + + HLOGP(qrlog.Debug, "Spawning Group TSBPD thread"); +#if ENABLE_HEAVY_LOGGING + std::ostringstream tns1, tns2; + // Take the last 2 ciphers from the socket ID. + tns1 << id(); + std::string s = tns1.str(); + tns2 << "SRT:GLat:$" << s.substr(s.size()-2, 2); + + const string& tn = tns2.str(); + + ThreadName tnkeep(tn); + const string& thname = tn; +#else + const string thname = "SRT:GLat"; +#endif + if (!StartThread(m_RcvTsbPdThread, &CUDTGroup::tsbpd, this, thname)) + return -1; + } + + return 0; +} + +void* CUDTGroup::tsbpd(void* param) +{ + CUDTGroup* self = (CUDTGroup*)param; + + THREAD_STATE_INIT("SRT:GLat"); + + // Make the TSBPD thread a "client" of the group, + // which will ensure that the group will not be physically + // deleted until this thread exits. + // NOTE: DO NOT LEAD TO EVER CANCEL THE THREAD!!! + ScopedGroupKeeper gkeeper(self); + + CUniqueSync recvdata_cc(self->m_RcvDataLock, self->m_RcvDataCond); + CSync tsbpd_cc(self->m_RcvTsbPdCond, recvdata_cc.locker()); + + self->m_bWakeOnRecv = true; + while (!self->m_bClosing) + { + steady_clock::time_point tsNextDelivery; // Next packet delivery time + bool rxready = false; + + enterCS(self->m_RcvBufferLock); + const steady_clock::time_point tnow = steady_clock::now(); + + self->m_pRcvBuffer->updRcvAvgDataSize(tnow); + const srt::CRcvBufferNew::PacketInfo info = self->m_pRcvBuffer->getFirstValidPacketInfo(); + + const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); + tsNextDelivery = info.tsbpd_time; + + if (!self->m_bTLPktDrop) + { + rxready = !info.seq_gap && is_time_to_deliver; + } + else if (is_time_to_deliver) + { + rxready = true; + if (info.seq_gap) + { + const int iDropCnt SRT_ATR_UNUSED = self->rcvDropTooLateUpTo(info.seqno); +#if ENABLE_LOGGING + const int64_t timediff_us = count_microseconds(tnow - info.tsbpd_time); +#if ENABLE_HEAVY_LOGGING + HLOGC(tslog.Debug, + log << self->CONID() << "tsbpd: DROPSEQ: up to seqno %" << CSeqNo::decseq(info.seqno) << " (" + << iDropCnt << " packets) playable at " << FormatTime(info.tsbpd_time) << " delayed " + << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') << (timediff_us % 1000) + << " ms"); +#endif + LOGC(brlog.Warn, + log << self->CONID() << "RCV-DROPPED " << iDropCnt << " packet(s). Packet seqno %" << info.seqno + << " delayed for " << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') + << (timediff_us % 1000) << " ms"); +#endif + + tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. + } + } + leaveCS(self->m_RcvBufferLock); + + if (rxready) + { + HLOGC(tslog.Debug, + log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << info.seqno << " (belated " + << (count_milliseconds(steady_clock::now() - info.tsbpd_time)) << "ms)"); + /* + * There are packets ready to be delivered + * signal a waiting "recv" call if there is any data available + */ + if (self->m_bSynRecving) + { + recvdata_cc.notify_one(); + } + /* + * Set EPOLL_IN to wakeup any thread waiting on epoll + */ + CUDT::uglobal().m_EPoll.update_events(self->id(), self->m_sPollID, SRT_EPOLL_IN, true); + CGlobEvent::triggerEvent(); + tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. + } + + if (!is_zero(tsNextDelivery)) + { + IF_HEAVY_LOGGING(const steady_clock::duration timediff = tsNextDelivery - tnow); + /* + * Buffer at head of queue is not ready to play. + * Schedule wakeup when it will be. + */ + self->m_bWakeOnRecv = false; + HLOGC(tslog.Debug, + log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << info.seqno + << " T=" << FormatTime(tsNextDelivery) << " - waiting " << count_milliseconds(timediff) << "ms"); + THREAD_PAUSED(); + tsbpd_cc.wait_until(tsNextDelivery); + THREAD_RESUMED(); + } + else + { + /* + * We have just signaled epoll; or + * receive queue is empty; or + * next buffer to deliver is not in receive queue (missing packet in sequence). + * + * Block until woken up by one of the following event: + * - All ready-to-play packets have been pulled and EPOLL_IN cleared (then loop to block until next pkt time + * if any) + * - New buffers ACKed + * - Closing the connection + */ + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: no data, scheduling wakeup at ack"); + self->m_bWakeOnRecv = true; + THREAD_PAUSED(); + tsbpd_cc.wait(); + THREAD_RESUMED(); + } + + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP!!!"); + } + THREAD_EXIT(); + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: EXITING"); + return NULL; +} + #endif diff --git a/srtcore/group.h b/srtcore/group.h index d06fd8b83..390c31886 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -114,6 +114,8 @@ class CUDTGroup CUDTGroup(SRT_GROUP_TYPE); ~CUDTGroup(); + void createBuffers(int32_t isn); + SocketData* add(SocketData data); struct HaveID @@ -325,6 +327,14 @@ class CUDTGroup public: int recv(char* buf, int len, SRT_MSGCTRL& w_mc); + int recv_old(char* buf, int len, SRT_MSGCTRL& w_mc); + + // XXX not sure if taking time here is right + bool isRcvBufferReady() const + { + srt::sync::ScopedLock lck(m_RcvBufferLock); + return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); + } void close(); @@ -379,7 +389,7 @@ class CUDTGroup /// @param ack The past-the-last-received ACK sequence number void readyPackets(srt::CUDT* core, int32_t ack); - void syncWithSocket(const srt::CUDT& core, const HandshakeSide side); + void syncWithFirstSocket(const srt::CUDT& core, const HandshakeSide side); int getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize); int getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize); @@ -443,9 +453,8 @@ class CUDTGroup void erase(gli_t it); }; GroupContainer m_Group; - const bool m_bSyncOnMsgNo; // It goes into a dedicated HS field. Could be true for balancing groups (not implemented). SRT_GROUP_TYPE m_type; - CUDTSocket* m_listener; // A "group" can only have one listener. + CUDTSocket* m_listener; // A "group" can only have one listener. XXX unsure what this is for actually srt::sync::atomic m_iBusy; CallbackHolder m_cbConnectHook; void installConnectHook(srt_connect_callback_fn* hook, void* opaq) @@ -615,8 +624,37 @@ class CUDTGroup // typedef StaticBuffer senderBuffer_t; private: + +#if ENABLE_NEW_RCVBUFFER + UniquePtr m_pSndBuffer; // XXX for future. + UniquePtr m_pRcvBuffer; + + sync::CThread m_RcvTsbPdThread; // Rcv TsbPD Thread handle + + static void* tsbpd(void* param); + + struct ScopedGroupKeeper + { + CUDTGroup* me; + + ScopedGroupKeeper(CUDTGroup* meme): me(meme) + { + srt::sync::ScopedLock lk(me->m_GroupLock); + me->apiAcquire(); + } + + ~ScopedGroupKeeper() + { + srt::sync::ScopedLock lk(me->m_GroupLock); + me->apiRelease(); + } + }; + friend ScopedGroupKeeper; + +#endif + // Fields required for SRT_GTYPE_BACKUP groups. - senderBuffer_t m_SenderBuffer; + senderBuffer_t m_SenderBuffer; // This mechanism is to be removed on group-common sndbuf int32_t m_iSndOldestMsgNo; // oldest position in the sender buffer sync::atomic m_iSndAckedMsgNo; uint32_t m_uOPT_MinStabilityTimeout_us; @@ -641,6 +679,9 @@ class CUDTGroup int m_iSndTimeOut; // sending timeout in milliseconds int m_iRcvTimeOut; // receiving timeout in milliseconds + bool m_bOPT_MessageAPI; // XXX false not supported + int m_iOPT_RcvBufSize; + // Start times for TsbPd. These times shall be synchronized // between all sockets in the group. The first connected one // defines it, others shall derive it. The value 0 decides if @@ -673,25 +714,48 @@ class CUDTGroup /// @throws CUDTException(MJ_AGAIN, MN_RDAVAIL, 0) std::vector recv_WaitForReadReady(const std::vector& aliveMembers, std::set& w_broken); +#if ENABLE_NEW_RCVBUFFER + + sync::atomic m_RcvLastSeqNo; + +#else // This is the sequence number of a packet that has been previously // delivered. Initially it should be set to SRT_SEQNO_NONE so that the sequence read // from the first delivering socket will be taken as a good deal. sync::atomic m_RcvBaseSeqNo; + // This is used in the system of reading packets indirectly through + // the socket's reading system, each one operating independently through TSBPD + // in live mode and range completeness in file mode. Not used in common buffer mode. +#endif + bool m_bOpened; // Set to true when at least one link is at least pending bool m_bConnected; // Set to true on first link confirmed connected bool m_bClosing; + bool stillConnected() + { + return m_bOpened + && m_bConnected + && !m_bClosing; + } + // There's no simple way of transforming config // items that are predicted to be used on socket. // Use some options for yourself, store the others // for setting later on a socket. std::vector m_config; +#if ENABLE_NEW_RCVBUFFER // Signal for the blocking user thread that the packet // is ready to deliver. - sync::Condition m_RcvDataCond; sync::Mutex m_RcvDataLock; + sync::Condition m_RcvDataCond; + sync::Condition m_RcvTsbPdCond; + sync::atomic m_bWakeOnRecv; +#endif + mutable sync::Mutex m_RcvBufferLock; // Protects the state of the m_pRcvBuffer + sync::atomic m_iLastSchedSeqNo; // represetnts the value of CUDT::m_iSndNextSeqNo for each running socket sync::atomic m_iLastSchedMsgNo; @@ -813,13 +877,22 @@ class CUDTGroup void resetInitialRxSequence() { +#if !ENABLE_NEW_RCVBUFFER // The app-reader doesn't care about the real sequence number. // The first provided one will be taken as a good deal; even if // this is going to be past the ISN, at worst it will be caused // by TLPKTDROP. m_RcvBaseSeqNo = SRT_SEQNO_NONE; +#endif } +#if ENABLE_NEW_RCVBUFFER + int checkLazySpawnLatencyThread(); + int addDataUnit(CUnit* u); + bool checkPacketArrivalLoss(const CPacket& rpkt, typename CUDT::loss_seqs_t::value_type&); + int rcvDropTooLateUpTo(int seqno); +#endif + bool applyGroupTime(time_point& w_start_time, time_point& w_peer_start_time) { using srt::sync::is_zero; @@ -858,6 +931,14 @@ class CUDTGroup void updateLatestRcv(srt::CUDTSocket*); +#if ENABLE_NEW_RCVBUFFER + SRT_ATR_NODISCARD bool updateSendPacketUnique(int32_t single_seq); + SRT_ATR_NODISCARD bool updateSendPacketLoss(const std::vector< std::pair >& seqlist); + + SRT_ATR_NODISCARD bool getSendSchedule(SocketData* d, std::vector& w_sched); + void discardSendSchedule(SocketData* d, int ndiscard); +#endif + // Property accessors SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRTSOCKET, id, m_GroupID); SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRTSOCKET, peerid, m_PeerGroupID); @@ -865,7 +946,6 @@ class CUDTGroup SRTU_PROPERTY_RW_CHAIN(CUDTGroup, int32_t, currentSchedSequence, m_iLastSchedSeqNo); SRTU_PROPERTY_RRW(std::set&, epollset, m_sPollID); SRTU_PROPERTY_RW_CHAIN(CUDTGroup, int64_t, latency, m_iTsbPdDelay_us); - SRTU_PROPERTY_RO(bool, synconmsgno, m_bSyncOnMsgNo); SRTU_PROPERTY_RO(bool, closing, m_bClosing); }; diff --git a/srtcore/group_common.cpp b/srtcore/group_common.cpp index 2176f7742..65f8599a6 100644 --- a/srtcore/group_common.cpp +++ b/srtcore/group_common.cpp @@ -23,7 +23,7 @@ namespace srt namespace groups { -SocketData prepareSocketData(CUDTSocket* s) +SocketData prepareSocketData(CUDTSocket* s, SRT_GROUP_TYPE type) { // This uses default SRT_GST_BROKEN because when the group operation is done, // then the SRT_GST_IDLE state automatically turns into SRT_GST_RUNNING. This is @@ -53,10 +53,12 @@ SocketData prepareSocketData(CUDTSocket* s) false, false, false, + type == SRT_GTYPE_BALANCING ? true : false, // use_send_schedule 0, // load_factor 0, // unit_load 0, // weight - 0 // pktSndDropTotal + 0, // pktSndDropTotal + {} }; return sd; } diff --git a/srtcore/group_common.h b/srtcore/group_common.h index be4773e6a..ab3048f5e 100644 --- a/srtcore/group_common.h +++ b/srtcore/group_common.h @@ -16,11 +16,13 @@ Written by #ifndef INC_SRT_GROUP_COMMON_H #define INC_SRT_GROUP_COMMON_H +#include +#include + #include "srt.h" #include "common.h" #include "core.h" - -#include +#include "buffer.h" namespace srt { @@ -28,6 +30,24 @@ namespace groups { typedef SRT_MEMBERSTATUS GroupState; + enum SeqType + { + SQT_FRESH, + SQT_LOSS, + SQT_PFILTER + }; + + struct SchedSeq + { + int32_t seq; + SeqType type; + + bool operator == (const SchedSeq& other) const + { + return seq == other.seq && type == other.type; + } + }; + struct SocketData { SRTSOCKET id; // same as ps->m_SocketID @@ -45,17 +65,24 @@ namespace groups bool ready_error; // Balancing data + bool use_send_schedule; double load_factor; double unit_load; + // Configuration uint16_t weight; // Stats int64_t pktSndDropTotal; + + // This is used only in balancing mode and it defines + // sequence numbers of packets to be sent at the next request + // from packData() for a socket that belongs to a balancing group. + std::deque send_schedule; }; - SocketData prepareSocketData(CUDTSocket* s); + SocketData prepareSocketData(CUDTSocket* s, SRT_GROUP_TYPE type); typedef std::list group_t; typedef group_t::iterator gli_t; diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 316e77ed8..bab852905 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -126,6 +126,7 @@ srt::CUnitQueue::CQEntry* srt::CUnitQueue::allocateEntry(const int iNumUnits, co for (int i = 0; i < iNumUnits; ++i) { tempu[i].m_iFlag = CUnit::FREE; + tempu[i].m_pParentQueue = this; tempu[i].m_Packet.m_pcData = tempb + i * mss; } diff --git a/srtcore/queue.h b/srtcore/queue.h index e77e1ea7c..87b8ab477 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -130,7 +130,7 @@ class CUnitQueue /// @param iNumUnits a number of units to allocate /// @param mss the size of each unit in bytes. /// @return a pointer to a newly allocated entry on success, NULL otherwise. - static CQEntry* allocateEntry(const int iNumUnits, const int mss); + CQEntry* allocateEntry(const int iNumUnits, const int mss); private: CQEntry* m_pQEntry; // pointer to the first unit queue diff --git a/srtcore/socketconfig.cpp b/srtcore/socketconfig.cpp index a49d1d639..f64674efa 100644 --- a/srtcore/socketconfig.cpp +++ b/srtcore/socketconfig.cpp @@ -52,6 +52,27 @@ written by #include "srt.h" #include "socketconfig.h" +namespace srt +{ +int RcvBufferSizeOptionToValue(int val, int flightflag, int mss) +{ + // Mimimum recv buffer size is 32 packets + const int mssin_size = mss - CPacket::UDP_HDR_SIZE; + + int bufsize; + if (val > mssin_size * CSrtConfig::DEF_MIN_FLIGHT_PKT) + bufsize = val / mssin_size; + else + bufsize = CSrtConfig::DEF_MIN_FLIGHT_PKT; + + // recv buffer MUST not be greater than FC size + if (bufsize > flightflag) + bufsize = flightflag; + + return bufsize; +} +} + using namespace srt; extern const int32_t SRT_DEF_VERSION = SrtParseVersion(SRT_VERSION); @@ -122,17 +143,7 @@ struct CSrtConfigSetter if (val <= 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - // Mimimum recv buffer size is 32 packets - const int mssin_size = co.iMSS - CPacket::UDP_HDR_SIZE; - - if (val > mssin_size * co.DEF_MIN_FLIGHT_PKT) - co.iRcvBufSize = val / mssin_size; - else - co.iRcvBufSize = co.DEF_MIN_FLIGHT_PKT; - - // recv buffer MUST not be greater than FC size - if (co.iRcvBufSize > co.iFlightFlagSize) - co.iRcvBufSize = co.iFlightFlagSize; + co.iRcvBufSize = srt::RcvBufferSizeOptionToValue(val, co.iFlightFlagSize, co.iMSS); } }; diff --git a/srtcore/socketconfig.h b/srtcore/socketconfig.h index 3fbf0e264..807defb73 100644 --- a/srtcore/socketconfig.h +++ b/srtcore/socketconfig.h @@ -346,6 +346,9 @@ inline bool cast_optval(const void* optval, int optlen) return false; } + +int RcvBufferSizeOptionToValue(int optval, int flightflag, int mss); + } // namespace srt struct SRT_SocketOptionObject diff --git a/srtcore/srt_attr_defs.h b/srtcore/srt_attr_defs.h index 2b7e07d3d..6959e7775 100644 --- a/srtcore/srt_attr_defs.h +++ b/srtcore/srt_attr_defs.h @@ -85,10 +85,17 @@ used by SRT library internally. // Macro shortcut for implementing parts of the redundancy features // that require the new receiver buffer -#if defined(ENABLE_BONDING) && defined(ENABLE_NEW_RCVBUFFER) -#define ENABLE_NEW_BONDING 1 +#if defined(ENABLE_BONDING) + #if defined(ENABLE_NEW_RCVBUFFER) + #define ENABLE_NEW_BONDING 1 + #define ENABLE_OLD_BONDING 0 + #else + #define ENABLE_OLD_BONDING 1 + #define ENABLE_NEW_BONDING 0 + #endif #else -#define ENABLE_NEW_BONDING 0 + #define ENABLE_NEW_BONDING 0 + #define ENABLE_OLD_BONDING 0 #endif /////////////////////////////////////////////////////////////////////////////// diff --git a/srtcore/sync.h b/srtcore/sync.h index 260dc7191..f1992e79f 100644 --- a/srtcore/sync.h +++ b/srtcore/sync.h @@ -489,6 +489,7 @@ inline void releaseCond(Condition& cv) { cv.destroy(); } // This should provide a cleaner API around locking with debug-logging inside. class CSync { +protected: Condition* m_cond; UniqueLock* m_locker; @@ -536,23 +537,41 @@ class CSync } // Static ad-hoc version - static void lock_signal(Condition& cond, Mutex& m) + static void lock_signal(Condition& cond, Mutex& m) { ScopedLock lk(m); // XXX with thread logging, don't use ScopedLock directly! cond.notify_one(); } + static void lock_notify_one(Condition& cond, Mutex& m) + { + ScopedLock lk(m); // XXX with thread logging, don't use ScopedLock directly! + cond.notify_one(); + } + + static void lock_broadcast(Condition& cond, Mutex& m) { ScopedLock lk(m); // XXX with thread logging, don't use ScopedLock directly! cond.notify_all(); } + static void lock_notify_all(Condition& cond, Mutex& m) + { + ScopedLock lk(m); // XXX with thread logging, don't use ScopedLock directly! + cond.notify_all(); + } + void signal_locked(UniqueLock& lk SRT_ATR_UNUSED) { // EXPECTED: lk.mutex() is LOCKED. m_cond->notify_one(); } + void notify_one_locked(UniqueLock& lk SRT_ATR_UNUSED) + { + // EXPECTED: lk.mutex() is LOCKED. + m_cond->notify_one(); + } // The signal_relaxed and broadcast_relaxed functions are to be used in case // when you don't care whether the associated mutex is locked or not (you @@ -567,9 +586,14 @@ class CSync // comment is provided to explain, why the use of the relaxed signaling is // correctly used. - void signal_relaxed() { signal_relaxed(*m_cond); } + void signal_relaxed() { notify_one_relaxed(*m_cond); } + void notify_one_relaxed() { notify_one_relaxed(*m_cond); } + static void signal_relaxed(Condition& cond) { cond.notify_one(); } + static void notify_one_relaxed(Condition& cond) { cond.notify_one(); } + static void broadcast_relaxed(Condition& cond) { cond.notify_all(); } + static void notify_all_relaxed(Condition& cond) { cond.notify_all(); } }; //////////////////////////////////////////////////////////////////////////////// @@ -586,6 +610,7 @@ class CEvent public: Mutex& mutex() { return m_lock; } + Condition& cond() { return m_cond; } public: /// Causes the current thread to block until @@ -631,6 +656,49 @@ class CEvent }; +// This function binds together the functionality of +// UniqueLock and CSync. It provides a simple interface of CSync +// while having already the UniqueLock applied in the scope, +// so a safe statement can be made about the mutex being locked. +// +// With this class you can also use CEvent class as a holder +// for the mutex and condition pair. + +class CUniqueSync: public CSync +{ + UniqueLock m_ulock; + +public: + + UniqueLock& locker() { return m_ulock; } + + CUniqueSync(Mutex& mut, Condition& cnd) + : CSync(cnd, m_ulock) + , m_ulock(mut) + { + } + + CUniqueSync(CEvent& event) + : CSync(event.cond(), m_ulock) + , m_ulock(event.mutex()) + { + } + + // These functions can be used safely because + // this whole class guarantees that whatever happens + // while its object exists is that the mutex is locked. + + void notify_one() + { + m_cond->notify_one(); + } + + void notify_all() + { + m_cond->notify_all(); + } +}; + class CTimer { public: diff --git a/test/test_buffer.cpp b/test/test_buffer.cpp index 1dd95c55c..35d93f7b5 100644 --- a/test/test_buffer.cpp +++ b/test/test_buffer.cpp @@ -33,7 +33,7 @@ class CRcvBufferReadMsg #if ENABLE_NEW_RCVBUFFER const bool enable_msg_api = m_use_message_api; const bool enable_peer_rexmit = true; - m_rcv_buffer.reset(new CRcvBufferNew(m_init_seqno, m_buff_size_pkts, m_unit_queue.get(), enable_msg_api)); + m_rcv_buffer.reset(new CRcvBufferNew(m_init_seqno, m_buff_size_pkts, enable_msg_api)); m_rcv_buffer->setPeerRexmitFlag(enable_peer_rexmit); #else m_rcv_buffer.reset(new CRcvBuffer(m_unit_queue.get(), m_buff_size_pkts)); @@ -78,7 +78,10 @@ class CRcvBufferReadMsg } #if ENABLE_NEW_RCVBUFFER - return m_rcv_buffer->insert(unit); + auto info = m_rcv_buffer->insert(unit); + // XXX extra checks? + + return int(info.result); #else const int offset = CSeqNo::seqoff(m_first_unack_seqno, seqno); return m_rcv_buffer->addData(unit, offset); From 027072a9cf6be4433cf100807bb82e44018fe382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Wed, 24 Aug 2022 09:14:09 +0200 Subject: [PATCH 04/62] Fixed referring to buffer pointer in the socket --- srtcore/api.cpp | 21 +++ srtcore/buffer_rcv.cpp | 32 +++- srtcore/buffer_rcv.h | 13 +- srtcore/core.cpp | 391 +++++++++++++++++++++++++++-------------- srtcore/group.cpp | 38 +++- srtcore/group.h | 24 ++- 6 files changed, 370 insertions(+), 149 deletions(-) diff --git a/srtcore/api.cpp b/srtcore/api.cpp index df5873209..c727e662c 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -148,6 +148,19 @@ void srt::CUDTSocket::setBrokenClosed() bool srt::CUDTSocket::readReady() { +#if ENABLE_NEW_BONDING + + // In the "new bonding" the reading from a socket + // happens exclusively from the group and the socket is + // only used as a connection point, packet dispatching + // and single link management. Data buffering and hence + // ability to deliver a packet through API is exclusively + // the matter of group, therefore a single socket is never + // "read ready". + + if (m_GroupOf) + return false; +#endif // TODO: Use m_RcvBufferLock here (CUDT::isRcvReadReady())? if (m_UDT.m_bConnected && m_UDT.m_pRcvBuffer->isRcvDataReady()) return true; @@ -2249,6 +2262,8 @@ int srt::CUDTUnited::select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSE return count; } +// XXX This may crash when a member socket is added to selectEx. +// Consider revising to prevent a socket from being used. int srt::CUDTUnited::selectEx(const vector& fds, vector* readfds, vector* writefds, @@ -2589,6 +2604,12 @@ void srt::CUDTUnited::checkBrokenSockets() // NOT WHETHER THEY ARE ALSO READY TO PLAY at the time when // this function is called (isRcvDataReady also checks if the // available data is "ready to play"). + + // Additional note on group receiver: with the new group + // receiver m_pRcvBuffer in the socket core is NULL always, + // but that's not a problem - you can close the member socket + // safely without worrying about reading data because they are + // in the group anyway. #if ENABLE_NEW_RCVBUFFER && s->core().m_pRcvBuffer->hasAvailablePackets() #else diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 0b0fb6a88..72754bdaf 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -116,6 +116,31 @@ CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) IF_RCVBUF_DEBUG(scoped_log.ss << " msgno " << unit->m_Packet.getMsgSeq(m_bPeerRexmitFlag)); IF_RCVBUF_DEBUG(scoped_log.ss << " m_iStartSeqNo " << m_iStartSeqNo << " offset " << offset); + int32_t avail_seq; + int avail_range; + + // Calculation done for the sake of possible discrepancy + // in order to inform the caller what to do. + if (m_entries[m_iStartPos].status == EntryState_Avail) + { + avail_seq = packetAt(m_iStartPos).getSeqNo(); + avail_range = m_iEndPos - m_iStartPos; + } + else if (m_iDropPos != m_iEndPos) + { + avail_seq = SRT_SEQNO_NONE; + avail_range = 0; + } + else + { + avail_seq = packetAt(m_iDropPos).getSeqNo(); + + // We don't know how many packets follow it exactly, + // but in this case it doesn't matter. We know that + // at least one is there. + avail_range = 1; + } + if (offset < 0) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -2"); @@ -125,7 +150,7 @@ CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) if (offset >= (int)capacity()) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -3"); - return InsertInfo(InsertInfo::DISCREPANCY); + return InsertInfo(InsertInfo::DISCREPANCY, avail_seq, avail_range); } // TODO: Don't do assert here. Process this situation somehow. @@ -268,6 +293,7 @@ CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) if (m_entries[m_iStartPos].pUnit && m_entries[m_iStartPos].status == EntryState_Avail) { avail_packet = &packetAt(m_iStartPos); + avail_range = m_iEndPos - m_iStartPos; } else if (!m_tsbpd.isEnabled() && m_iFirstRandomMsgPos != -1) { @@ -276,16 +302,18 @@ CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) // the only "next deliverable" is the first complete message that satisfies // the order requirement. avail_packet = &packetAt(m_iFirstRandomMsgPos); + avail_range = 1; } else if (m_iDropPos != m_iEndPos) { avail_packet = &packetAt(m_iDropPos); + avail_range = 1; } IF_RCVBUF_DEBUG(scoped_log.ss << " returns 0 (OK)"); if (avail_packet) - return InsertInfo(InsertInfo::INSERTED, avail_packet->getSeqNo(), earlier_time); + return InsertInfo(InsertInfo::INSERTED, avail_packet->getSeqNo(), avail_range, earlier_time); else return InsertInfo(InsertInfo::INSERTED); // No packet candidate (NOTE: impossible in live mode) } diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 3ece1638b..15c3eb5ff 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -230,15 +230,16 @@ class CRcvBufferNew int first_seq; // sequence of the first available readable packet time_point first_time; // Time of the new, earlier packet that appeared ready, or null-time if this didn't change. + int avail_range; - InsertInfo(Result r, int fp_seq = SRT_SEQNO_NONE, + InsertInfo(Result r, int fp_seq = SRT_SEQNO_NONE, int range = 0, time_point fp_time = time_point()) - : result(r), first_seq(fp_seq), first_time(fp_time) + : result(r), first_seq(fp_seq), first_time(fp_time), avail_range(range) { } InsertInfo() - : result(REDUNDANT), first_seq(SRT_SEQNO_NONE) + : result(REDUNDANT), first_seq(SRT_SEQNO_NONE), avail_range(0) { } @@ -516,6 +517,10 @@ class CRcvBufferNew entries_t m_entries; const size_t m_szSize; // size of the array of units (buffer) + + //XXX removed. In this buffer the units may come from various different + // queues, and the unit has a pointer pointing to the queue from which + // it comes, and it should be returned to the same queue. //CUnitQueue* m_pUnitQueue; // the shared unit queue int m_iStartSeqNo; @@ -557,6 +562,8 @@ class CRcvBufferNew time_point getTsbPdTimeBase(uint32_t usPktTimestamp) const; void updateTsbPdTimeBase(uint32_t usPktTimestamp); + bool isTsbPd() const { return m_tsbpd.isEnabled(); } + /// Form a string of the current buffer fullness state. /// number of packets acknowledged, TSBPD readiness, etc. std::string strFullnessState(int iFirstUnackSeqNo, const time_point& tsNow) const; diff --git a/srtcore/core.cpp b/srtcore/core.cpp index a7661e6f6..b1011c901 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -2179,7 +2179,7 @@ int srt::CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t bytelen, uint // SRT_HS_LATENCY_SND is the value that the peer proposes to be the // value used by agent when receiving data. We take this as a local latency value. - peer_decl_latency = SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]); + peer_decl_latency = SRT_HS_LATENCY_SND::unwrap(latencystr); } // Use the maximum latency out of latency from our settings and the latency @@ -3286,6 +3286,8 @@ void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) << " PST=" << FormatTime(m_tsRcvPeerStartTime)); } +#if !ENABLE_NEW_RCVBUFFER + steady_clock::time_point rcv_buffer_time_base; bool rcv_buffer_wrap_period = false; steady_clock::duration rcv_buffer_udrift(0); @@ -3307,9 +3309,13 @@ void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) // time to not fill a network window. enterCS(m_RecvLock); m_pRcvBuffer->applyGroupTime(rcv_buffer_time_base, rcv_buffer_wrap_period, m_iTsbPdDelay_ms * 1000, rcv_buffer_udrift); -#if ENABLE_NEW_RCVBUFFER - m_pRcvBuffer->setPeerRexmitFlag(m_bPeerRexmitFlag); -#endif + +// This didn't make sense anyway. The SRT clients from before version 1.3 +// do not support bonding, so this function wouldn't even be called when +// connecting to them. +//#if ENABLE_NEW_RCVBUFFER +// m_pRcvBuffer->setPeerRexmitFlag(m_bPeerRexmitFlag); +//#endif leaveCS(m_RecvLock); HLOGF(gmlog.Debug, "AFTER HS: Set Rcv TsbPd mode: delay=%u.%03us GROUP TIME BASE: %s%s", @@ -3328,6 +3334,8 @@ void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) // with updateAfterSrtHandshake(). updateSrtSndSettings(); +#endif + /* if (gp->synconmsgno()) { @@ -5169,6 +5177,13 @@ void * srt::CUDT::tsbpd(void* param) THREAD_STATE_INIT("SRT:TsbPd"); + if (!self->m_pRcvBuffer) + { + LOGC(tslog.Fatal, log << "IPE: started CUDT::tsbpd() thread in a group mode without socket's receiver buffer"); + THREAD_EXIT(); + return 0; + } + CUniqueSync recvdata_lcc (self->m_RecvLock, self->m_RecvDataCond); CSync tsbpd_cc(self->m_RcvTsbPdCond, recvdata_lcc.locker()); @@ -5279,6 +5294,7 @@ void * srt::CUDT::tsbpd(void* param) return NULL; } +// This is to be called from tsbpd(). int srt::CUDT::rcvDropTooLateUpTo(int seqno) { // Make sure that it would not drop over m_iRcvCurrSeqNo, which may break senders. @@ -5554,6 +5570,7 @@ void srt::CUDT::setInitialRcvSeq(int32_t isn) #endif } +// Called from tsbpd(). void srt::CUDT::updateForgotten(int seqlen, int32_t lastack, int32_t skiptoseqno) { enterCS(m_StatsLock); @@ -6831,6 +6848,15 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_MESSAGE, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0); +#if ENABLE_NEW_BONDING + // In this code configuration, this function shall not be used + // if the socket is a group member. + + if (m_parent->m_GroupOf) + throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI); + +#endif + UniqueLock recvguard (m_RecvLock); CSync tscond (m_RcvTsbPdCond, recvguard); @@ -8602,23 +8628,35 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr updateCC(TEV_ACKACK, EventVariant(ack)); + bool giveup = false; + +#if ENABLE_BONDING + CUDTUnited::GroupKeeper gk (uglobal(), m_parent); +#endif + +#if ENABLE_NEW_BONDING + // Group receiver in use - see if the drift update should + // be done in the group. If so, don't check anything in the socket + if (gk.group) + { + giveup = true; + gk.group->addGroupDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, rtt); + } +#endif + // This function will put a lock on m_RecvLock by itself, as needed. // It must be done inside because this function reads the current time // and if waiting for the lock has caused a delay, the time will be // inaccurate. Additionally it won't lock if TSBPD mode is off, and // won't update anything. Note that if you set TSBPD mode and use // srt_recvfile (which doesn't make any sense), you'll have a deadlock. - if (m_config.bDriftTracer) + if (!giveup && m_config.bDriftTracer && m_pRcvBuffer) { const bool drift_updated SRT_ATR_UNUSED = m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, rtt); -#if ENABLE_BONDING - if (drift_updated && m_parent->m_GroupOf) +#if ENABLE_OLD_BONDING + if (drift_updated && gk.group) { - ScopedLock glock(uglobal().m_GlobControlLock); - if (m_parent->m_GroupOf) - { - m_parent->m_GroupOf->synchronizeDrift(this); - } + m_parent->m_GroupOf->synchronizeDrift(this); } #endif } @@ -8913,6 +8951,13 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) { const int32_t* dropdata = (const int32_t*) ctrlpkt.m_pcData; +#if ENABLE_NEW_BONDING + + // NOTE: a connected socket that once had a buffer cannot + // lose it before being closed. An unconnected socket (including broken) + // cannot be dispatched the UMSG_DROPREQ message to. + if (!m_parent->m_GroupOf && m_pRcvBuffer) +#endif { CUniqueSync rcvtscc (m_RecvLock, m_RcvTsbPdCond); // With both TLPktDrop and TsbPd enabled, a message always consists only of one packet. @@ -9083,8 +9128,19 @@ void srt::CUDT::processCtrl(const CPacket &ctrlpkt) } } +// Called only for the old buffer with groups. void srt::CUDT::updateSrtRcvSettings() { +#if ENABLE_NEW_BONDING + CUDTUnited::GroupKeeper gk(uglobal(), m_parent); + + if (gk.group) + { + // TSBPD mode in case of groups will be set during syncWithFirstSocket. + return; + } + +#endif // CHANGED: we need to apply the tsbpd delay only for socket TSBPD. // For Group TSBPD the buffer will have to deliver packets always on request // by sequence number, although the buffer will have to solve all the TSBPD @@ -9099,8 +9155,7 @@ void srt::CUDT::updateSrtRcvSettings() m_pRcvBuffer->setPeerRexmitFlag(m_bPeerRexmitFlag); #endif - // XXX m_bGroupTsbPd is ignored with SRT_ENABLE_APP_READER - if (m_bTsbPd || m_bGroupTsbPd) + if (m_bTsbPd) { #if ENABLE_NEW_RCVBUFFER m_pRcvBuffer->setTsbPdMode(m_tsRcvPeerStartTime, false, milliseconds_from(m_iTsbPdDelay_ms)); @@ -10006,6 +10061,12 @@ int srt::CUDT::processData(CUnit* in_unit) m_iEXPCount = 1; m_tsLastRspTime.store(steady_clock::now()); + // Keep the group alive until the end of this function. + +#if ENABLE_BONDING + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); +#endif + // We are receiving data, start tsbpd thread if TsbPd is enabled if (-1 == checkLazySpawnLatencyThread()) { @@ -10037,21 +10098,35 @@ int srt::CUDT::processData(CUnit* in_unit) } #if ENABLE_HEAVY_LOGGING - { - steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) - - // It's easier to remove the latency factor from this value than to add a function - // that exposes the details basing on which this value is calculated. - steady_clock::time_point pts = m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); - steady_clock::time_point ets = pts - tsbpddelay; - - HLOGC(qrlog.Debug, log << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() - << " seq=" << packet.getSeqNo() - // XXX FIX IT. OTS should represent the original sending time, but it's relative. - //<< " OTS=" << FormatTime(packet.getMsgTimeStamp()) - << " ETS=" << FormatTime(ets) - << " PTS=" << FormatTime(pts)); - } + + steady_clock::time_point pts; // will be needed in further logging + + { + steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) + + // It's easier to remove the latency factor from this value than to add a function + // that exposes the details basing on which this value is calculated. + +#if ENABLE_NEW_BONDING + // Block this for a case of new-bonding group, as m_pRcvBuffer is NULL there. + if (gkeeper.group) + { + pts = gkeeper.group->getPktTsbPdTime(packet.getMsgTimeStamp()); + } + else +#endif + { + pts = m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); + } + steady_clock::time_point ets = pts - tsbpddelay; + + HLOGC(qrlog.Debug, log << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() + << " seq=" << packet.getSeqNo() + // XXX FIX IT. OTS should represent the original sending time, but it's relative. + //<< " OTS=" << FormatTime(packet.getMsgTimeStamp()) + << " ETS=" << FormatTime(ets) + << " PTS=" << FormatTime(pts)); + } #endif updateCC(TEV_RECEIVE, EventVariant(&packet)); @@ -10114,14 +10189,19 @@ int srt::CUDT::processData(CUnit* in_unit) if (packet.getMsgSeq() != SRT_MSGNO_CONTROL) // disregard filter-control packets, their seq may mean nothing { int diff = CSeqNo::seqoff(m_iRcvCurrPhySeqNo, packet.m_iSeqNo); - // Difference between these two sequence numbers is expected to be: - // 0 - duplicated last packet (theory only) - // 1 - subsequent packet (alright) - // <0 - belated or recovered packet - // >1 - jump over a packet loss (loss = seqdiff-1) - if (diff > 1) + // Difference between these two sequence numbers is expected to be: + // 0 - duplicated last packet (theory only) + // 1 - subsequent packet (alright) + // <0 - belated or recovered packet + // >1 - jump over a packet loss (loss = seqdiff-1) + + // Hook on non-NULL receiver buffer for a case of ENABLE_NEW_BONDING. + // XXX This is for stats only and for groups it can be done elsewhere. + if (m_pRcvBuffer && diff > 1) { const int loss = diff - 1; // loss is all that is above diff == 1 + + ScopedLock lg(m_StatsLock); const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); m_stats.rcvr.lost.count(stats::BytesPackets(loss * avgpayloadsz, (uint32_t) loss)); @@ -10148,9 +10228,8 @@ int srt::CUDT::processData(CUnit* in_unit) // accepted or rejected because if it was belated it may result in a // "runaway train" problem as the IDLE links are being updated the base // reception sequence pointer stating that this link is not receiving. - if (m_parent->m_GroupOf) + if (gkeeper.group) { - ScopedLock protect_group_existence (uglobal().m_GlobControlLock); groups::SocketData* gi = m_parent->m_GroupMemberData; // This check is needed as after getting the lock the socket @@ -10215,6 +10294,9 @@ int srt::CUDT::processData(CUnit* in_unit) CUnit * u = *unitIt; CPacket &rpkt = u->m_Packet; + int buffer_add_result; + bool adding_successful = true; + // m_iRcvLastSkipAck is the base sequence number for the receiver buffer. // This is the offset in the buffer; if this is negative, it means that // this sequence is already in the past and the buffer is not interested. @@ -10227,10 +10309,9 @@ int srt::CUDT::processData(CUnit* in_unit) if (offset < 0) { IF_HEAVY_LOGGING(exc_type = "BELATED"); - steady_clock::time_point tsbpdtime = m_pRcvBuffer->getPktTsbPdTime(rpkt.getMsgTimeStamp()); const double bltime = (double) CountIIR( uint64_t(m_stats.traceBelatedTime) * 1000, - count_microseconds(steady_clock::now() - tsbpdtime), 0.2); + count_microseconds(steady_clock::now() - pts), 0.2); enterCS(m_StatsLock); m_stats.traceBelatedTime = bltime / 1000.0; @@ -10242,100 +10323,114 @@ int srt::CUDT::processData(CUnit* in_unit) continue; } - const int avail_bufsize = (int) getAvailRcvBufferSizeNoLock(); - if (offset >= avail_bufsize) + int avail_bufsize = 0; // needed in logging + + // This is executed only when bonding is enabled and only + // with the new buffer (in which case the buffer is in the group). +#if ENABLE_NEW_BONDING + if (gkeeper.group) { - // This is already a sequence discrepancy. Probably there could be found - // some way to make it continue reception by overriding the sequence and - // make a kinda TLKPTDROP, but there has been found no reliable way to do this. - if (m_bTsbPd && m_bTLPktDrop && m_pRcvBuffer->empty()) + // NOTE: this will lock ALSO the receiver buffer lock in the group + CRcvBufferNew::InsertInfo info = gkeeper.group->addDataUnit(u); + if (info.result == CRcvBufferNew::InsertInfo::DISCREPANCY) { - // Only in live mode. In File mode this shall not be possible - // because the sender should stop sending in this situation. - // In Live mode this means that there is a gap between the - // lowest sequence in the empty buffer and the incoming sequence - // that exceeds the buffer size. Receiving data in this situation - // is no longer possible and this is a point of no return. - - LOGC(qrlog.Error, log << CONID() << - "SEQUENCE DISCREPANCY. BREAKING CONNECTION." - " seq=" << rpkt.m_iSeqNo - << " buffer=(" << m_iRcvLastSkipAck - << ":" << m_iRcvCurrSeqNo // -1 = size to last index - << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, int(m_pRcvBuffer->capacity()) - 1) - << "), " << (offset-avail_bufsize+1) - << " past max. Reception no longer possible. REQUESTING TO CLOSE."); - - // This is a scoped lock with AckLock, but for the moment - // when processClose() is called this lock must be taken out, - // otherwise this will cause a deadlock. We don't need this - // lock anymore, and at 'return' it will be unlocked anyway. - recvbuf_acklock.unlock(); - processClose(); + if (m_bGroupTsbPd && info.avail_range == 0) + { + LOGC(qrlog.Error, log << CONID() << + "SEQUENCE DISCREPANCY. BREAKING CONNECTION."); + + recvbuf_acklock.unlock(); + processClose(); + } + else + { + // Can't reach the buffer information because it's inside the group. + // The log should be likely fully presented in the CUDTGroup::addDataUnit(). + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo); + } return -1; } - else + + buffer_add_result = int(info.result); + } + else +#endif + { + avail_bufsize = (int) getAvailRcvBufferSizeNoLock(); + if (offset >= avail_bufsize) { + // This is already a sequence discrepancy. Probably there could be found + // some way to make it continue reception by overriding the sequence and + // make a kinda TLKPTDROP, but there has been found no reliable way to do this. + if (m_bTsbPd && m_bTLPktDrop && m_pRcvBuffer->empty()) + { + // Only in live mode. In File mode this shall not be possible + // because the sender should stop sending in this situation. + // In Live mode this means that there is a gap between the + // lowest sequence in the empty buffer and the incoming sequence + // that exceeds the buffer size. Receiving data in this situation + // is no longer possible and this is a point of no return. + + LOGC(qrlog.Error, log << CONID() << + "SEQUENCE DISCREPANCY. BREAKING CONNECTION." + " seq=" << rpkt.m_iSeqNo + << " buffer=(" << m_iRcvLastSkipAck + << ":" << m_iRcvCurrSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, int(m_pRcvBuffer->capacity()) - 1) + << "), " << (offset-avail_bufsize+1) + << " past max. Reception no longer possible. REQUESTING TO CLOSE."); + + // This is a scoped lock with AckLock, but for the moment + // when processClose() is called this lock must be taken out, + // otherwise this will cause a deadlock. We don't need this + // lock anymore, and at 'return' it will be unlocked anyway. + recvbuf_acklock.unlock(); + processClose(); + return -1; + } + else + { #if ENABLE_NEW_RCVBUFFER - LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo - << ", insert offset " << offset << ". " - << m_pRcvBuffer->strFullnessState(m_iRcvLastAck, steady_clock::now()) - ); + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo + << ", insert offset " << offset << ". " + << m_pRcvBuffer->strFullnessState(m_iRcvLastAck, steady_clock::now()) + ); #else - LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo - << ", insert offset " << offset << ". " - << m_pRcvBuffer->strFullnessState(steady_clock::now()) - ); + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo + << ", insert offset " << offset << ". " + << m_pRcvBuffer->strFullnessState(steady_clock::now()) + ); #endif - return -1; + return -1; + } } - } - bool adding_successful = true; - - int buffer_add_result; #if ENABLE_NEW_RCVBUFFER - #if ENABLE_BONDING + CRcvBufferNew::InsertInfo info = m_pRcvBuffer->insert(u); - { // XXX superfluous lock, if this socket is not a group member; revise! - ScopedLock protect_group_existence (uglobal().m_GlobControlLock); + // Remember this value in order to CHECK if there's a need + // to request triggering TSBPD in case when TSBPD is in the + // state of waiting forever and wants to know if there's any + // possible time to wake up known earlier than that. - if (m_parent->m_GroupOf) - { - // NOTE: this will lock ALSO the receiver buffer lock in the group - buffer_add_result = m_parent->m_GroupOf->addDataUnit(u); - } - else - { - CRcvBufferNew::InsertInfo info = m_pRcvBuffer->insert(u); - - // Remember this value in order to CHECK if there's a need - // to request triggering TSBPD in case when TSBPD is in the - // state of waiting forever and wants to know if there's any - // possible time to wake up known earlier than that. - - // Note that in case of the "builtin group reader" (its own - // buffer), there's no need to do it here because it has also - // its own TSBPD thread. + // Note that in case of the "builtin group reader" (its own + // buffer), there's no need to do it here because it has also + // its own TSBPD thread. - if (info.result == CRcvBufferNew::InsertInfo::INSERTED) - { - // This may happen multiple times in the loop, so update only if earlier. - if (next_tsbpd_avail == time_point() || next_tsbpd_avail > info.first_time) - next_tsbpd_avail = info.first_time; - new_inserted = true; - } - buffer_add_result = int(info.result); + if (info.result == CRcvBufferNew::InsertInfo::INSERTED) + { + // This may happen multiple times in the loop, so update only if earlier. + if (next_tsbpd_avail == time_point() || next_tsbpd_avail > info.first_time) + next_tsbpd_avail = info.first_time; + new_inserted = true; } - - } - #else - buffer_add_result = m_pRcvBuffer->insert(u); - #endif + buffer_add_result = int(info.result); #else - buffer_add_result = m_pRcvBuffer->addData(u, offset); + buffer_add_result = m_pRcvBuffer->addData(u, offset); #endif + } + if (buffer_add_result < 0) { // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. @@ -10376,14 +10471,24 @@ int srt::CUDT::processData(CUnit* in_unit) else expectspec << "ACCEPTED"; - LOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo - << " offset=" << offset - << " BUFr=" << avail_bufsize + std::ostringstream bufinfo; + + if (m_pRcvBuffer) + { + bufinfo << " BUFr=" << avail_bufsize << " avail=" << getAvailRcvBufferSizeNoLock() << " buffer=(" << m_iRcvLastSkipAck << ":" << m_iRcvCurrSeqNo // -1 = size to last index << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, m_pRcvBuffer->capacity()-1) - << ") " + << ")"; + } + + // Empty buffer info in case of groupwise receiver. + // There's no way to obtain this information here. + + LOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo + << " offset=" << offset + << bufinfo.str() << " RSL=" << expectspec.str() << " SN=" << rexmitstat[pktrexmitflag] << " FLAGS: " @@ -10394,9 +10499,6 @@ int srt::CUDT::processData(CUnit* in_unit) // Otherwise it's an error. if (adding_successful) { - // XXX move this code do CUDT::defaultPacketArrival and call it from here??? - // srt_loss_seqs = CALLBACK_CALL(m_cbPacketArrival, rpkt); - HLOGC(qrlog.Debug, log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo)); @@ -10407,15 +10509,12 @@ int srt::CUDT::processData(CUnit* in_unit) typename loss_seqs_t::value_type this_loss; #if ENABLE_NEW_RCVBUFFER + if (gkeeper.group) { - ScopedLock protect_group_existence (uglobal().m_GlobControlLock); - if (m_parent->m_GroupOf) - { - // This should take out assymmetric losses, and leave only - // valid ones. This also reports the losses to the group. - have_loss = m_parent->m_GroupOf->checkPacketArrivalLoss(rpkt, (this_loss)); - handled = true; - } + // This should take out assymmetric losses, and leave only + // valid ones. This also reports the losses to the group. + have_loss = gkeeper.group->checkPacketArrivalLoss(rpkt, (this_loss)); + handled = true; } #endif @@ -10672,6 +10771,37 @@ int srt::CUDT::processData(CUnit* in_unit) } #if ENABLE_BONDING +#if ENABLE_NEW_RCVBUFFER +void srt::CUDT::updateIdleLinkFrom(CUDT* source) +{ + ScopedLock lg (m_RecvLock); + + // Same version as the one with the old receiver buffering, + // just this time don't check if the buffer is empty. The value + // will be rejected anyway if it's equal. + + // XXX Try to optimize this. Note that here happens: + // - decseq just to have a value to compare directly + // - seqcmp with that value + // - if passed, in setInitialRcvSeq there's the same decseq again + int32_t new_last_rcv = CSeqNo::decseq(source->m_iRcvLastSkipAck); + + // if (new_last_rcv <% m_iRcvCurrSeqNo) + if (CSeqNo::seqcmp(new_last_rcv, m_iRcvCurrSeqNo) < 0) + { + // Reject the change because that would shift the reception pointer backwards. + HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq in @" << m_SocketID + << ": backward setting rejected: %" << m_iRcvCurrSeqNo + << " -> %" << new_last_rcv); + return; + } + + HLOGC(grlog.Debug, log << "grp: updating rcv-seq in @" << m_SocketID + << " from @" << source->m_SocketID << ": %" << source->m_iRcvLastSkipAck); + setInitialRcvSeq(source->m_iRcvLastSkipAck); + +} +#else void srt::CUDT::updateIdleLinkFrom(CUDT* source) { ScopedLock lg (m_RecvLock); @@ -10702,6 +10832,7 @@ void srt::CUDT::updateIdleLinkFrom(CUDT* source) << " from @" << source->m_SocketID << ": %" << source->m_iRcvLastSkipAck); setInitialRcvSeq(source->m_iRcvLastSkipAck); } +#endif // XXX This function is currently unused. It should be fixed and put into use. // See the blocked call in CUDT::processData(). diff --git a/srtcore/group.cpp b/srtcore/group.cpp index bea0daaae..1660ac90f 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -18,6 +18,7 @@ namespace srt { int32_t CUDTGroup::s_tokenGen = 0; +#if !ENABLE_NEW_RCVBUFFER // [[using locked(this->m_GroupLock)]]; bool CUDTGroup::getBufferTimeBase(CUDT* forthesakeof, steady_clock::time_point& w_tb, @@ -62,6 +63,7 @@ bool CUDTGroup::getBufferTimeBase(CUDT* forthesakeof, } return true; } +#endif // [[using locked(this->m_GroupLock)]]; bool CUDTGroup::applyGroupSequences(SRTSOCKET target, int32_t& w_snd_isn, int32_t& w_rcv_isn) @@ -967,16 +969,18 @@ void CUDTGroup::syncWithFirstSocket(const CUDT& core, const HandshakeSide side) // Get the latency (possibly fixed against the opposite side) // from the first socket (core.m_iTsbPdDelay_ms), - // and set it on the current socket. + // and set it on the group. set_latency(core.m_iTsbPdDelay_ms * int64_t(1000)); } #if ENABLE_NEW_RCVBUFFER -int CUDTGroup::addDataUnit(CUnit* u) +CRcvBufferNew::InsertInfo CUDTGroup::addDataUnit(CUnit* u) { - // XXX Likely some other things need to be done here. + // If this returns false, the adding has failed and + CRcvBufferNew::InsertInfo info; + const CPacket& rpkt = u->m_Packet; { ScopedLock lk (m_RcvBufferLock); @@ -996,8 +1000,18 @@ int CUDTGroup::addDataUnit(CUnit* u) tsbpd_cc.notify_all(); } } + else if (info.result == CRcvBufferNew::InsertInfo::DISCREPANCY) + { + LOGC(qrlog.Error, log << CONID() + << "SEQUENCE DISCREPANCY. DISCARDING." + << " seq=" << rpkt.m_iSeqNo + << " buffer=(" << m_pRcvBuffer->getStartSeqNo() + << ":" << m_RcvLastSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(m_pRcvBuffer->getStartSeqNo(), int(m_pRcvBuffer->capacity()) - 1) + << ")"); + } - return int(info.result); + return info; } @@ -2130,6 +2144,7 @@ void CUDTGroup::recv_CollectAliveAndBroken(vector& alive, set CUDTGroup::recv_WaitForReadReady(const vector& aliveMembers, set& w_broken) { if (aliveMembers.empty()) @@ -2253,7 +2268,6 @@ vector CUDTGroup::recv_WaitForReadReady(const vector& return readReady; } -#if !ENABLE_NEW_RCVBUFFER void CUDTGroup::updateReadState(SRTSOCKET /* not sure if needed */, int32_t sequence) { bool ready = false; @@ -2511,7 +2525,6 @@ int CUDTGroup::recv_old(char* buf, int len, SRT_MSGCTRL& w_mc) // The REAL version for the new group receiver. // - int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) { CUniqueSync tscond (m_RcvDataLock, m_RcvDataCond); @@ -3366,6 +3379,7 @@ const char* CUDTGroup::StateStr(CUDTGroup::GroupState st) return unknown; } +#if !ENABLE_NEW_RCVBUFFER void CUDTGroup::synchronizeDrift(const srt::CUDT* srcMember) { SRT_ASSERT(srcMember != NULL); @@ -3400,6 +3414,7 @@ void CUDTGroup::synchronizeDrift(const srt::CUDT* srcMember) member.m_pRcvBuffer->applyGroupDrift(timebase, wrap_period, udrift); } } +#endif void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) { @@ -5321,6 +5336,17 @@ void CUDTGroup::processKeepalive(CUDTGroup::SocketData* gli, const CPacket& ctrl } +#if ENABLE_NEW_RCVBUFFER +void CUDTGroup::addGroupDriftSample(uint32_t timestamp, const time_point& tsArrival, int rtt) +{ + if (!m_bOPT_DriftTracer) + return; + + ScopedLock lck(m_RcvBufferLock); + m_pRcvBuffer->addRcvTsbPdDriftSample(timestamp, tsArrival, rtt); +} +#endif + void CUDTGroup::internalKeepalive(SocketData* gli) { // This is in response to AGENT SENDING keepalive. This means that there's diff --git a/srtcore/group.h b/srtcore/group.h index afff139d3..eeecf9a0a 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -712,6 +712,11 @@ class CUDTGroup void recv_CollectAliveAndBroken(std::vector& w_alive, std::set& w_broken); +#if ENABLE_NEW_RCVBUFFER + + sync::atomic m_RcvLastSeqNo; + +#else /// The function polls alive member sockets and retrieves a list of read-ready. /// [acquires lock for CUDT::uglobal()->m_GlobControlLock] /// [[using locked(m_GroupLock)]] temporally unlocks-locks internally @@ -721,11 +726,6 @@ class CUDTGroup /// @throws CUDTException(MJ_AGAIN, MN_RDAVAIL, 0) std::vector recv_WaitForReadReady(const std::vector& aliveMembers, std::set& w_broken); -#if ENABLE_NEW_RCVBUFFER - - sync::atomic m_RcvLastSeqNo; - -#else // This is the sequence number of a packet that has been previously // delivered. Initially it should be set to SRT_SEQNO_NONE so that the sequence read // from the first delivering socket will be taken as a good deal. @@ -895,9 +895,10 @@ class CUDTGroup #if ENABLE_NEW_RCVBUFFER int checkLazySpawnLatencyThread(); - int addDataUnit(CUnit* u); + CRcvBufferNew::InsertInfo addDataUnit(CUnit* u); bool checkPacketArrivalLoss(const CPacket& rpkt, typename CUDT::loss_seqs_t::value_type&); int rcvDropTooLateUpTo(int seqno); + void addGroupDriftSample(uint32_t timestamp, const time_point& tsArrival, int rtt); #endif bool applyGroupTime(time_point& w_start_time, time_point& w_peer_start_time) @@ -929,13 +930,15 @@ class CUDTGroup } // Live state synchronization +#if !ENABLE_NEW_RCVBUFFER bool getBufferTimeBase(srt::CUDT* forthesakeof, time_point& w_tb, bool& w_wp, duration& w_dr); - bool applyGroupSequences(SRTSOCKET, int32_t& w_snd_isn, int32_t& w_rcv_isn); - /// @brief Synchronize TSBPD base time and clock drift among members using the @a srcMember as a reference. /// @param srcMember a reference for synchronization. void synchronizeDrift(const srt::CUDT* srcMember); +#endif + bool applyGroupSequences(SRTSOCKET, int32_t& w_snd_isn, int32_t& w_rcv_isn); + void updateLatestRcv(srt::CUDTSocket*); #if ENABLE_NEW_RCVBUFFER @@ -944,6 +947,11 @@ class CUDTGroup SRT_ATR_NODISCARD bool getSendSchedule(SocketData* d, std::vector& w_sched); void discardSendSchedule(SocketData* d, int ndiscard); + + time_point getPktTsbPdTime(uint32_t usPktTimestamp) const + { + return m_pRcvBuffer->getPktTsbPdTime(usPktTimestamp); + } #endif // Property accessors From 7a5853b1863dce54c6b0f9b3f2cf7410ab096eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Wed, 24 Aug 2022 17:24:57 +0200 Subject: [PATCH 05/62] Fixed some bux during the first run --- srtcore/buffer_rcv.cpp | 49 ++++++++++++++-------------- srtcore/core.cpp | 69 ++++++++++++++++++++++++++++++++++++++-- srtcore/group.cpp | 6 ++-- srtcore/group.h | 6 ++++ srtcore/group_common.cpp | 2 +- srtcore/list.cpp | 6 ++++ 6 files changed, 107 insertions(+), 31 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 72754bdaf..7752e9c74 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -119,28 +119,6 @@ CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) int32_t avail_seq; int avail_range; - // Calculation done for the sake of possible discrepancy - // in order to inform the caller what to do. - if (m_entries[m_iStartPos].status == EntryState_Avail) - { - avail_seq = packetAt(m_iStartPos).getSeqNo(); - avail_range = m_iEndPos - m_iStartPos; - } - else if (m_iDropPos != m_iEndPos) - { - avail_seq = SRT_SEQNO_NONE; - avail_range = 0; - } - else - { - avail_seq = packetAt(m_iDropPos).getSeqNo(); - - // We don't know how many packets follow it exactly, - // but in this case it doesn't matter. We know that - // at least one is there. - avail_range = 1; - } - if (offset < 0) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -2"); @@ -150,6 +128,29 @@ CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) if (offset >= (int)capacity()) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -3"); + + // Calculation done for the sake of possible discrepancy + // in order to inform the caller what to do. + if (m_entries[m_iStartPos].status == EntryState_Avail) + { + avail_seq = packetAt(m_iStartPos).getSeqNo(); + avail_range = m_iEndPos - m_iStartPos; + } + else if (m_iDropPos == m_iEndPos) + { + avail_seq = SRT_SEQNO_NONE; + avail_range = 0; + } + else + { + avail_seq = packetAt(m_iDropPos).getSeqNo(); + + // We don't know how many packets follow it exactly, + // but in this case it doesn't matter. We know that + // at least one is there. + avail_range = 1; + } + return InsertInfo(InsertInfo::DISCREPANCY, avail_seq, avail_range); } @@ -304,7 +305,7 @@ CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) avail_packet = &packetAt(m_iFirstRandomMsgPos); avail_range = 1; } - else if (m_iDropPos != m_iEndPos) + else if (m_iDropPos == m_iEndPos) { avail_packet = &packetAt(m_iDropPos); avail_range = 1; @@ -856,7 +857,7 @@ CRcvBufferNew::PacketInfo CRcvBufferNew::getFirstValidPacketInfo() const if (m_entries[m_iStartPos].status == EntryState_Avail) { SRT_ASSERT(m_entries[m_iStartPos].pUnit); - return (PacketInfo) { m_iStartSeqNo, false /*no gap*/, getPktTsbPdTime(packetAt(m_iStartSeqNo).getMsgTimeStamp()) }; + return (PacketInfo) { m_iStartSeqNo, false /*no gap*/, getPktTsbPdTime(packetAt(m_iStartPos).getMsgTimeStamp()) }; } // If not, get the information from the drop if (m_iDropPos != m_iEndPos) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index b1011c901..507cc985f 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -6805,6 +6805,13 @@ int srt::CUDT::recvmsg2(char* data, int len, SRT_MSGCTRL& w_mctrl) size_t srt::CUDT::getAvailRcvBufferSizeLock() const { +#if ENABLE_NEW_BONDING + CUDTUnited::GroupKeeper gk (uglobal(), m_parent); + if (gk.group) + { + return gk.group->getAvailBufSize(m_iRcvLastAck); + } +#endif ScopedLock lck(m_RcvBufferLock); return getAvailRcvBufferSizeNoLock(); } @@ -6812,6 +6819,13 @@ size_t srt::CUDT::getAvailRcvBufferSizeLock() const size_t srt::CUDT::getAvailRcvBufferSizeNoLock() const { #if ENABLE_NEW_RCVBUFFER + +// This function is to be used instrumentally for +// cases under the socket's buffer lock. NOT TO BE USED +// for new bonding. +#if ENABLE_BONDING + SRT_ASSERT(m_parent->m_GroupOf == NULL); +#endif return m_pRcvBuffer->getAvailSize(m_iRcvLastAck); #else return m_pRcvBuffer->getAvailBufSize(); @@ -6820,6 +6834,11 @@ size_t srt::CUDT::getAvailRcvBufferSizeNoLock() const bool srt::CUDT::isRcvBufferReady() const { +#if ENABLE_NEW_BONDING + // In "new bonding" (new rcvbuf + bonding) + // this function should never be called for member sockets. + SRT_ASSERT(m_parent->m_GroupOf == NULL); +#endif ScopedLock lck(m_RcvBufferLock); #if ENABLE_NEW_RCVBUFFER return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); @@ -8010,6 +8029,30 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) return nbsent; } + // Lock the group existence until this function ends. This will be useful + // also on other places. +#if ENABLE_BONDING + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); +#if ENABLE_NEW_RCVBUFFER + + // bonding + new rcvbuffer : group buffering if member + const bool group_buffering = gkeeper.group; +#else + // bonding + old rcvbuffer : no group buffering + const bool group_buffering = false; +#endif + +#else + // no bonding : no group buffering + const bool group_buffering = false; +#endif + + int avail_receiver_buffer_size = 0; +#if ENABLE_NEW_BONDING + if (group_buffering) + avail_receiver_buffer_size = gkeeper.group->getAvailBufSize(ack); +#endif + // There are new received packets to acknowledge, update related information. /* tsbpd thread may also call ackData when skipping packet so protect code */ UniqueLock bufflock(m_RcvBufferLock); @@ -8037,13 +8080,17 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) // required in the defined order. At present we only need the lock // on m_GlobControlLock to prevent the group from being deleted // in the meantime - if (m_parent->m_GroupOf) + if (gkeeper.group) { // Check is first done before locking to avoid unnecessary // mutex locking. The condition for this field is that it // can be either never set, already reset, or ever set // and possibly dangling. The re-check after lock eliminates // the dangling case. + + // This lock is NOT necessary to keep the group existing, + // but is necessary for having the socket's membership not + // cleared in the meantime. ScopedLock glock (uglobal().m_GlobControlLock); // Note that updateLatestRcv will lock m_GroupOf->m_GroupLock, @@ -8159,6 +8206,11 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) // also known as ACKD_TOTAL_SIZE_VER100. int32_t data[ACKD_TOTAL_SIZE]; + // For "new bonding", still get this size from buffer, + // but only unless we have a group + if (!group_buffering) + avail_receiver_buffer_size = (int) getAvailRcvBufferSizeNoLock(); + // Case you care, CAckNo::incack does exactly the same thing as // CSeqNo::incseq. Logically the ACK number is a different thing // than sequence number (it's a "journal" for ACK request-response, @@ -8168,8 +8220,11 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) data[ACKD_RCVLASTACK] = m_iRcvLastAck; data[ACKD_RTT] = m_iSRTT; data[ACKD_RTTVAR] = m_iRTTVar; - data[ACKD_BUFFERLEFT] = (int) getAvailRcvBufferSizeNoLock(); + data[ACKD_BUFFERLEFT] = avail_receiver_buffer_size; // a minimum flow window of 2 is used, even if buffer is full, to break potential deadlock + // XXX This could be better fixed by having the receiver constantly send + // ACKs even with the same ACK number, while the buffer is still full, and + // stop only after sending at least one ACK with nonzero avail size. if (data[ACKD_BUFFERLEFT] < 2) data[ACKD_BUFFERLEFT] = 2; @@ -10099,10 +10154,11 @@ int srt::CUDT::processData(CUnit* in_unit) #if ENABLE_HEAVY_LOGGING - steady_clock::time_point pts; // will be needed in further logging + steady_clock::time_point pts; { steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) + pts = tsbpddelay; // It's easier to remove the latency factor from this value than to add a function // that exposes the details basing on which this value is calculated. @@ -10113,6 +10169,13 @@ int srt::CUDT::processData(CUnit* in_unit) { pts = gkeeper.group->getPktTsbPdTime(packet.getMsgTimeStamp()); } + else if (!m_pRcvBuffer) + { + // Somehow we have dispatched to a previous member socket, + // that was already removed from the group, which means that + // it is being closed now. Pretend nothing has been dispatched. + return -1; + } else #endif { diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 1660ac90f..0fd7927da 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -310,7 +310,7 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) void CUDTGroup::createBuffers(int32_t isn) { // XXX NOT YET, but will be in use. - m_pSndBuffer = NULL; + m_pSndBuffer.reset(); m_pRcvBuffer.reset(new srt::CRcvBufferNew(isn, m_iOPT_RcvBufSize, /*m_pRcvQueue->m_pUnitQueue, */ m_bOPT_MessageAPI)); } @@ -949,7 +949,7 @@ void CUDTGroup::syncWithFirstSocket(const CUDT& core, const HandshakeSide side) differ in sender and receiver. */ - m_RcvLastSeqNo = core.ISN(); + m_RcvLastSeqNo = CSeqNo::decseq(core.ISN()); createBuffers(core.ISN()); #else @@ -1075,7 +1075,7 @@ bool CUDTGroup::checkPacketArrivalLoss(const CPacket& rpkt, typename CUDT::loss_ bool have = false; - if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_RcvLastSeqNo) > 0)) + if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_RcvLastSeqNo)) > 0) { int32_t seqlo = CSeqNo::incseq(m_RcvLastSeqNo); int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); diff --git a/srtcore/group.h b/srtcore/group.h index eeecf9a0a..a12694f52 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -340,6 +340,12 @@ class CUDTGroup return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); } + size_t getAvailBufSize(int32_t last_ack) const + { + srt::sync::ScopedLock lck(m_RcvBufferLock); + return m_pRcvBuffer->getAvailSize(last_ack); + } + #endif void close(); diff --git a/srtcore/group_common.cpp b/srtcore/group_common.cpp index 65f8599a6..fae27c80c 100644 --- a/srtcore/group_common.cpp +++ b/srtcore/group_common.cpp @@ -58,7 +58,7 @@ SocketData prepareSocketData(CUDTSocket* s, SRT_GROUP_TYPE type) 0, // unit_load 0, // weight 0, // pktSndDropTotal - {} + std::deque() // Could be {}, but in C++11. }; return sd; } diff --git a/srtcore/list.cpp b/srtcore/list.cpp index 212599595..01b034781 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -117,6 +117,9 @@ int srt::CSndLossList::insert(int32_t seqno1, int32_t seqno2) return 0; } + // Make sure that seqno2 isn't earlier than seqno1. + SRT_ASSERT(CSeqNo::seqcmp(seqno1, seqno2) <= 0); + const int inserted_range = CSeqNo::seqlen(seqno1, seqno2); if (inserted_range <= 0 || inserted_range >= m_iSize) { LOGC(qslog.Error, log << "IPE: Tried to insert too big range of seqno: " << inserted_range << ". Ignoring. " @@ -506,6 +509,9 @@ srt::CRcvLossList::~CRcvLossList() void srt::CRcvLossList::insert(int32_t seqno1, int32_t seqno2) { + // Make sure that seqno2 isn't earlier than seqno1. + SRT_ASSERT(CSeqNo::seqcmp(seqno1, seqno2) <= 0); + // Data to be inserted must be larger than all those in the list if (m_iLargestSeq != SRT_SEQNO_NONE && CSeqNo::seqcmp(seqno1, m_iLargestSeq) <= 0) { From 6862b974de9bcf4de1dc3cb22714b5bd7211381c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 25 Aug 2022 17:12:47 +0200 Subject: [PATCH 06/62] Fixed getting group status with the reading function --- srtcore/group.cpp | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/srtcore/group.cpp b/srtcore/group.cpp index c22f896ba..0ef746dd8 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -2536,11 +2536,21 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) { HLOGC(arlog.Debug, log << CONID() << "grp:recv: CONNECTION BROKEN - reading from recv buffer just for formality"); - enterCS(m_RcvBufferLock); - const int res = (m_pRcvBuffer->isRcvDataReady(steady_clock::now())) - ? m_pRcvBuffer->readMessage(data, len, &w_mctrl) - : 0; - leaveCS(m_RcvBufferLock); + int as_result = 0; + bool ready = m_pRcvBuffer->isRcvDataReady(steady_clock::now()); + + if (ready) + { + ScopedLock lk (m_RcvBufferLock); + as_result = m_pRcvBuffer->readMessage(data, len, (&w_mctrl)); + } + + { + ScopedLock lk (m_GroupLock); + fillGroupData((w_mctrl), w_mctrl); + } + + const int res = as_result; w_mctrl.srctime = 0; @@ -2577,11 +2587,21 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) { HLOGC(arlog.Debug, log << CONID() << "grp:recv: BEGIN ASYNC MODE. Going to extract payload size=" << len); - enterCS(m_RcvBufferLock); - const int res = (m_pRcvBuffer->isRcvDataReady(steady_clock::now())) - ? m_pRcvBuffer->readMessage(data, len, (&w_mctrl), (&seqrange)) - : 0; - leaveCS(m_RcvBufferLock); + int as_result = 0; + bool ready = m_pRcvBuffer->isRcvDataReady(steady_clock::now()); + + if (ready) + { + ScopedLock lk (m_RcvBufferLock); + as_result = m_pRcvBuffer->readMessage(data, len, (&w_mctrl), (&seqrange)); + } + + { + ScopedLock lk (m_GroupLock); + fillGroupData((w_mctrl), w_mctrl); + } + + const int res = as_result; HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (NON-BLOCKING) result=" << res); @@ -2696,6 +2716,12 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) leaveCS(m_RcvBufferLock); HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (BLOCKING) result=" << res); + { + ScopedLock lk (m_GroupLock); + fillGroupData((w_mctrl), w_mctrl); + } + + if (m_bClosing) { throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); From 087b8dbac123e2f5caa924bd7579d14c3d6f41ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Tue, 30 Aug 2022 17:17:36 +0200 Subject: [PATCH 07/62] Fixed bux in receiver buffer. Fixed handshake resource creation order. Fixed epoll usage for reception for groups. --- srtcore/api.cpp | 23 ++++++- srtcore/buffer_rcv.cpp | 45 +++++++++++++- srtcore/core.cpp | 102 +++++++++++++++++++------------ srtcore/core.h | 4 +- srtcore/group.cpp | 133 +++++++++++++++++++++++++++++------------ srtcore/group.h | 13 ++-- srtcore/handshake.h | 2 + srtcore/sync.h | 2 +- srtcore/tsbpd_time.cpp | 11 +++- srtcore/utilities.h | 9 +++ 10 files changed, 259 insertions(+), 85 deletions(-) diff --git a/srtcore/api.cpp b/srtcore/api.cpp index c727e662c..417020a54 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -719,6 +719,16 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, g->m_bConnected = true; } + // In the new recvbuffer mode (and common receiver buffer) there's no waiting for reception + // on a socket and no reading from a socket directly is being done; instead the reading API + // is directly bound to the group and reading happens directly from the group's buffer. + // This includes also a situation of a newly connected socket, which will be delivering packets + // into the same common receiver buffer for the group, so readable will be the group itself + // when it has its own common buffer read-ready, by whatever reason. Packets to the buffer + // will be delivered by the sockets' receiver threads, so all these things happen strictly + // in the background. +#if !ENABLE_NEW_RCVBUFFER + // XXX PROLBEM!!! These events are subscribed here so that this is done once, lazily, // but groupwise connections could be accepted from multiple listeners for the same group! // m_listener MUST BE A CONTAINER, NOT POINTER!!! @@ -746,8 +756,11 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, // Both first accepted socket that makes the group-accept and every next // socket that adds a new link. int read_modes = SRT_EPOLL_IN | SRT_EPOLL_ERR; - int write_modes = SRT_EPOLL_OUT | SRT_EPOLL_ERR; epoll_add_usock_INTERNAL(g->m_RcvEID, ns, &read_modes); +#endif + + // Keep per-socket sender ready EID. + int write_modes = SRT_EPOLL_OUT | SRT_EPOLL_ERR; epoll_add_usock_INTERNAL(g->m_SndEID, ns, &write_modes); // With app reader, do not set groupPacketArrival (block the @@ -1510,7 +1523,9 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // connection succeeded or failed and whether the new socket is // ready to use or needs to be closed. epoll_add_usock_INTERNAL(g.m_SndEID, ns, &connect_modes); +#if !ENABLE_NEW_RCVBUFFER epoll_add_usock_INTERNAL(g.m_RcvEID, ns, &connect_modes); +#endif // Adding a socket on which we need to block to BOTH these tracking EIDs // and the blocker EID. We'll simply remove from them later all sockets that @@ -1632,7 +1647,9 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i f->sndstate = SRT_GST_BROKEN; f->rcvstate = SRT_GST_BROKEN; epoll_remove_socket_INTERNAL(g.m_SndEID, ns); +#if !ENABLE_NEW_RCVBUFFER epoll_remove_socket_INTERNAL(g.m_RcvEID, ns); +#endif } else { @@ -1718,7 +1735,9 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i epoll_remove_socket_INTERNAL(eid, y->second); epoll_remove_socket_INTERNAL(g.m_SndEID, y->second); +#if !ENABLE_NEW_RCVBUFFER epoll_remove_socket_INTERNAL(g.m_RcvEID, y->second); +#endif } } @@ -1758,7 +1777,9 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i epoll_remove_socket_INTERNAL(eid, s); epoll_remove_socket_INTERNAL(g.m_SndEID, s); +#if !ENABLE_NEW_RCVBUFFER epoll_remove_socket_INTERNAL(g.m_RcvEID, s); +#endif continue; } diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 7752e9c74..2550ce919 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -305,7 +305,7 @@ CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) avail_packet = &packetAt(m_iFirstRandomMsgPos); avail_range = 1; } - else if (m_iDropPos == m_iEndPos) + else if (m_iDropPos != m_iEndPos) { avail_packet = &packetAt(m_iDropPos); avail_range = 1; @@ -649,6 +649,49 @@ int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pai //updateNonreadPos(); } + // Now that we have m_iStartPos potentially shifted, reinitialize + // m_iEndPos and m_iDropPos. + + int pend_pos = incPos(m_iStartPos, m_iMaxPosOff); + + // First check: is anything in the beginning + if (m_entries[m_iStartPos].status == EntryState_Avail) + { + // If so, shift m_iEndPos up to the first nonexistent unit + // XXX Try to optimize search by splitting into two loops if necessary. + + m_iEndPos = incPos(m_iStartPos); + while (m_entries[m_iEndPos].status == EntryState_Avail) + { + m_iEndPos = incPos(m_iEndPos); + if (m_iEndPos == pend_pos) + break; + } + + // If we had first packet available, then there's also no drop pos. + m_iDropPos = m_iEndPos; + + } + else + { + // If not, reset m_iEndPos and search for the first after-drop candidate. + m_iEndPos = m_iStartPos; + m_iDropPos = m_iEndPos; + + while (m_entries[m_iDropPos].status != EntryState_Avail) + { + m_iDropPos = incPos(m_iDropPos); + if (m_iDropPos == pend_pos) + { + // Nothing found - set drop pos equal to end pos, + // which means there's no drop + m_iDropPos = m_iEndPos; + break; + } + } + } + + if (!m_tsbpd.isEnabled()) // We need updateFirstReadableRandom() here even if we are reading inorder, // incase readable inorder packets are all read out. diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 507cc985f..945575e14 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -3045,6 +3045,15 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A return false; } +#if ENABLE_NEW_RCVBUFFER + if (m_bTsbPd) + { + HLOGC(cnlog.Debug, log << "interpretGroup: socket TSBPD=on, switching to GROUP TSBPD"); + m_bGroupTsbPd = true; + m_bTsbPd = false; + } +#endif + ScopedLock guard_group_existence (uglobal().m_GlobControlLock); if (m_SrtHsSide == HSD_INITIATOR) @@ -4066,9 +4075,9 @@ EConnectStatus srt::CUDT::processRendezvous( return CONN_REJECT; } - // The CryptoControl must be created by the prepareConnectionObjects() before interpreting and creating HSv5 extensions - // because the it will be used there. - if (!prepareConnectionObjects(m_ConnRes, m_SrtHsSide, NULL)) + // The CryptoControl must be created before interpreting and creating HSv5 + // extensions because the it will be used there. + if (!createCrypter(m_SrtHsSide, m_ConnRes.v5())) { // m_RejectReason already handled HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to problems in prepareConnectionObjects."); @@ -4100,6 +4109,13 @@ EConnectStatus srt::CUDT::processRendezvous( return CONN_REJECT; } + if (!prepareBuffers(NULL)) + { + HLOGC(cnlog.Debug, + log << "processRendezvous: rejecting due to problems in prepareBuffers REQ-TIME: LOW."); + return CONN_REJECT; + } + updateAfterSrtHandshake(HS_VERSION_SRT1); // Pass on, inform about the shortened response-waiting period. @@ -4162,6 +4178,13 @@ EConnectStatus srt::CUDT::processRendezvous( // m_RejectReason is already set, so set the reqtype accordingly m_ConnReq.m_iReqType = URQFailure(m_RejectReason); } + + if (!prepareBuffers(NULL)) + { + HLOGC(cnlog.Debug, + log << "processRendezvous: rejecting due to problems in prepareBuffers REQ-TIME: LOW."); + return CONN_REJECT; + } } // This should be false, make a kinda assert here. if (needs_extension) @@ -4578,12 +4601,14 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, // so it will simply go the "old way". // (&&: skip if failed already) // Must be called before interpretSrtHandshake() to create the CryptoControl. - ok = ok && prepareConnectionObjects(m_ConnRes, m_SrtHsSide, eout); // May happen that 'response' contains a data packet that was sent in rendezvous mode. // In this situation the interpretation of handshake was already done earlier. ok = ok && pResponse->isControl(); + + ok = ok && createCrypter(m_SrtHsSide, m_ConnRes.v5()); ok = ok && interpretSrtHandshake(m_ConnRes, *pResponse, 0, 0); + ok = ok && prepareBuffers(eout); if (!ok) { @@ -5582,37 +5607,18 @@ void srt::CUDT::updateForgotten(int seqlen, int32_t lastack, int32_t skiptoseqno dropFromLossLists(lastack, CSeqNo::decseq(skiptoseqno)); //remove(from,to-inclusive) } -bool srt::CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout) +bool srt::CUDT::prepareBuffers(CUDTException *eout) { // This will be lazily created due to being the common // code with HSv5 rendezvous, in which this will be run // in a little bit "randomly selected" moment, but must // be run once in the whole connection process. - if (m_pSndBuffer) + if (m_pSndLossList) { HLOGC(rslog.Debug, log << "prepareConnectionObjects: (lazy) already created."); return true; } - // HSv5 is always bidirectional - const bool bidirectional = (hs.m_iVersion > HS_VERSION_UDT4); - - // HSD_DRAW is received only if this side is listener. - // If this side is caller with HSv5, HSD_INITIATOR should be passed. - // If this is a rendezvous connection with HSv5, the handshake role - // is taken from m_SrtHsSide field. - if (hsd == HSD_DRAW) - { - if (bidirectional) - { - hsd = HSD_RESPONDER; // In HSv5, listener is always RESPONDER and caller always INITIATOR. - } - else - { - hsd = m_config.bDataSender ? HSD_INITIATOR : HSD_RESPONDER; - } - } - try { m_pSndBuffer = new CSndBuffer(32, m_iMaxSRTPayloadSize); @@ -5648,12 +5654,6 @@ bool srt::CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd return false; } - if (!createCrypter(hsd, bidirectional)) // Make sure CC is created (lazy) - { - m_RejectReason = SRT_REJ_RESOURCE; - return false; - } - return true; } @@ -5712,10 +5712,11 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE; HLOGC(cnlog.Debug, log << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); - // Prepare all structures - if (!prepareConnectionObjects(w_hs, HSD_DRAW, 0)) + HandshakeSide hsd = w_hs.v5() || !m_config.bDataSender ? HSD_RESPONDER : HSD_INITIATOR; + + if (!createCrypter(hsd, w_hs.v5())) { - HLOGC(cnlog.Debug, log << "acceptAndRespond: prepareConnectionObjects failed - responding with REJECT."); + HLOGC(cnlog.Debug, log << "acceptAndRespond: createCrypter failed - responding with REJECT."); // If the SRT Handshake extension was provided and wasn't interpreted // correctly, the connection should be rejected. // @@ -5762,6 +5763,18 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } + if (!prepareBuffers(NULL)) + { + HLOGC(cnlog.Debug, log << "acceptAndRespond: prepareBuffers failed - responding with REJECT."); + // If the SRT Handshake extension was provided and wasn't interpreted + // correctly, the connection should be rejected. + // + // Respond with the rejection message and exit with exception + // so that the caller will know that this new socket should be deleted. + w_hs.m_iReqType = URQFailure(m_RejectReason); + throw CUDTException(MJ_SETUP, MN_REJECTED, 0); + } + // Synchronize the time NOW because the following function is about // to use the start time to pass it to the receiver buffer data. bool have_group = false; @@ -8138,7 +8151,15 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) #if ENABLE_NEW_RCVBUFFER // Locks m_RcvBufferLock, which is unlocked above by InvertedLock un_bufflock. // Must check read-readiness under m_RecvLock to protect the epoll from concurrent changes in readBuffer() - if (isRcvBufferReady()) + + // DO NOT check nor enable reading when a group member - group member sockets are never ready to read. + // XXX This is for the case of a group connection that is not TSBPD; the same thing + // should be done in the group, if this socket is a member. + + // Formally, for safety this should rather check the existence of m_pRcvBuffer. + SRT_ASSERT( bool(m_parent->m_GroupOf) != bool(m_pRcvBuffer) ); + + if (!m_parent->m_GroupOf && isRcvBufferReady()) #endif { if (m_config.bSynRecving) @@ -8160,6 +8181,10 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true); } } + + // This is done only for "old bonding" using the app-reader procedure. + // In the new bonding all buffer reception and reading ready state update + // happen exclusively inside the group. #if ENABLE_OLD_BONDING if (group_read_seq != SRT_SEQNO_NONE && m_parent->m_GroupOf) { @@ -10158,7 +10183,7 @@ int srt::CUDT::processData(CUnit* in_unit) { steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) - pts = tsbpddelay; + pts = steady_clock::time_point() + tsbpddelay; // It's easier to remove the latency factor from this value than to add a function // that exposes the details basing on which this value is calculated. @@ -11960,7 +11985,10 @@ void srt::CUDT::addEPoll(const int eid) return; enterCS(m_RecvLock); - if (isRcvBufferReady()) + // Check m_pRcvBuffer as now sockets can be also created + // without a receiver buffer, if they are new rcvbuffer group members. + // Such sockets never become readable. + if (m_pRcvBuffer && isRcvBufferReady()) { uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true); } diff --git a/srtcore/core.h b/srtcore/core.h index 6c2b2b4b6..4c66e5dd7 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -489,7 +489,7 @@ class CUDT /// Create the CryptoControl object based on the HS packet. Allocates sender and receiver buffers and loss lists. SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) - bool prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout); + bool prepareBuffers(CUDTException *eout); SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) EConnectStatus postConnect(const CPacket* response, bool rendezvous, CUDTException* eout) ATR_NOEXCEPT; @@ -938,7 +938,7 @@ class CUDT sync::CThread m_RcvTsbPdThread; // Rcv TsbPD Thread handle sync::Condition m_RcvTsbPdCond; // TSBPD signals if reading is ready. Use together with m_RecvLock - sync::atomic m_bWakeOnRecv; // Signal TsbPd thread on Ack sent + sync::atomic m_bWakeOnRecv; // Expected to be woken up when received a packet sync::Mutex m_RcvTsbPdStartupLock; // Protects TSBPD thread creating and joining CallbackHolder m_cbAcceptHook; diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 0ef746dd8..96210fa61 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -292,7 +292,9 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) setupMutex(m_GroupLock, "Group"); setupMutex(m_RcvDataLock, "RcvData"); setupCond(m_RcvDataCond, "RcvData"); +#if !ENABLE_NEW_RCVBUFFER m_RcvEID = m_Global.m_EPoll.create(&m_RcvEpolld); +#endif m_SndEID = m_Global.m_EPoll.create(&m_SndEpolld); m_stats.init(); @@ -307,18 +309,25 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) } #if ENABLE_NEW_RCVBUFFER -void CUDTGroup::createBuffers(int32_t isn) +void CUDTGroup::createBuffers(int32_t isn, const time_point& tsbpd_start_time) { // XXX NOT YET, but will be in use. m_pSndBuffer.reset(); m_pRcvBuffer.reset(new srt::CRcvBufferNew(isn, m_iOPT_RcvBufSize, /*m_pRcvQueue->m_pUnitQueue, */ m_bOPT_MessageAPI)); + if (tsbpd_start_time != time_point()) + { + HLOGC(gmlog.Debug, log << "grp/createBuffers: setting rcv buf start time=" << FormatTime(tsbpd_start_time) << " lat=" << latency_us() << "us"); + m_pRcvBuffer->setTsbPdMode(tsbpd_start_time, false, microseconds_from(latency_us())); + } } #endif CUDTGroup::~CUDTGroup() { +#if !ENABLE_NEW_RCVBUFFER srt_epoll_release(m_RcvEID); +#endif srt_epoll_release(m_SndEID); releaseMutex(m_GroupLock); releaseMutex(m_RcvDataLock); @@ -924,6 +933,14 @@ void CUDTGroup::syncWithFirstSocket(const CUDT& core, const HandshakeSide side) set_currentSchedSequence(core.ISN()); } + // Must be done here before createBuffers because the latency value + // will be used to set it to the buffer after creation. + HLOGC(gmlog.Debug, log << "grp/syncWithFirstSocket: setting group latency: " << core.m_iTsbPdDelay_ms << "ms"); + // Get the latency (possibly fixed against the opposite side) + // from the first socket (core.m_iTsbPdDelay_ms), + // and set it on the group. + set_latency_us(core.m_iTsbPdDelay_ms * int64_t(1000)); + #if ENABLE_NEW_RCVBUFFER /* FIX: In this implementation we need to initialize the receiver buffer. @@ -940,7 +957,16 @@ void CUDTGroup::syncWithFirstSocket(const CUDT& core, const HandshakeSide side) */ m_RcvLastSeqNo = CSeqNo::decseq(core.ISN()); - createBuffers(core.ISN()); + + if (core.m_bGroupTsbPd) + { + m_tsRcvPeerStartTime = core.m_tsRcvPeerStartTime; + } + + HLOGC(gmlog.Debug, log << "grp/syncWithFirstSocket: creating receiver buffer for ISN=%" << core.ISN() + << " TSBPD start: " << (core.m_bGroupTsbPd ? FormatTime(m_tsRcvPeerStartTime) : "not enabled")); + + createBuffers(core.ISN(), m_tsRcvPeerStartTime); #else // XXX @@ -957,10 +983,6 @@ void CUDTGroup::syncWithFirstSocket(const CUDT& core, const HandshakeSide side) resetInitialRxSequence(); #endif - // Get the latency (possibly fixed against the opposite side) - // from the first socket (core.m_iTsbPdDelay_ms), - // and set it on the group. - set_latency(core.m_iTsbPdDelay_ms * int64_t(1000)); } #if ENABLE_NEW_RCVBUFFER @@ -979,11 +1001,12 @@ CRcvBufferNew::InsertInfo CUDTGroup::addDataUnit(CUnit* u) if (info.result == CRcvBufferNew::InsertInfo::INSERTED) { - // If m_bWakeOnRecv, then notify anyway. + // If m_bTsbpdWaitForNewPacket, then notify anyway. // Otherwise notify only if a "fresher" packet was added, // so TSBPD should interrupt its sleep earlier and re-check. - if (m_bWakeOnRecv || info.first_time != time_point()) + if (m_bTsbpdWaitForNewPacket || info.first_time != time_point()) { + HLOGC(gmlog.Debug, log << "grp/addDataUnit: got a packet [live], SIGNAL TSBPD"); // Make a lock on data reception first, to protect the buffer. // Then notify TSBPD if required. CUniqueSync tsbpd_cc(m_RcvDataLock, m_RcvTsbPdCond); @@ -2066,6 +2089,8 @@ struct FLookupSocketWithEvent_LOCKED } }; +#if !ENABLE_HEAVY_LOGGING + void CUDTGroup::recv_CollectAliveAndBroken(vector& alive, set& broken) { #if ENABLE_HEAVY_LOGGING @@ -2134,7 +2159,6 @@ void CUDTGroup::recv_CollectAliveAndBroken(vector& alive, set CUDTGroup::recv_WaitForReadReady(const vector& aliveMembers, set& w_broken) { if (aliveMembers.empty()) @@ -2517,7 +2541,7 @@ int CUDTGroup::recv_old(char* buf, int len, SRT_MSGCTRL& w_mc) // int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) { - CUniqueSync tscond (m_RcvDataLock, m_RcvDataCond); + CUniqueSync tscond (m_RcvDataLock, m_RcvTsbPdCond); /* XXX DEBUG STUFF - enable when required char charbool[2] = {'0', '1'}; @@ -2557,7 +2581,7 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) { - HLOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup"); + HLOGP(tslog.Debug, "SIGNAL TSBPD thread to schedule wakeup FOR EXIT"); tscond.notify_all(); } else @@ -2611,7 +2635,7 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) { - HLOGP(arlog.Debug, "grp:recv: nothing to read, kicking TSBPD, return AGAIN"); + HLOGC(arlog.Debug, log << "grp:recv: nothing to read, SIGNAL TSBPD (" << (m_bTsbpdWaitForExtraction ? "" : "un") << "expected), return AGAIN"); tscond.notify_all(); } else @@ -2630,7 +2654,7 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) { - HLOGP(arlog.Debug, "grp:recv: DATA READ, but nothing more - kicking TSBPD."); + HLOGC(arlog.Debug, log << "grp:recv: ONE PACKET READ, but no more avail, SUGNAL TSBPD (" << (m_bTsbpdWaitForExtraction ? "" : "un") << "expected), return AGAIN"); tscond.notify_all(); } else @@ -2667,9 +2691,7 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) // would be "spurious". If a new packet comes ahead of the packet which's time is returned // in tstime (as TSBPD sleeps up to then), the procedure that receives it is responsible // of kicking TSBPD. - // bool spurious = (tstime != 0); - - HLOGC(tslog.Debug, log << CONID() << "grp:recv: KICK tsbpd"); + HLOGC(tslog.Debug, log << CONID() << "grp:recv: SIGNAL TSBPD" << (m_bTsbpdWaitForNewPacket ? " (spurious)" : "")); tscond.notify_one(); } @@ -2744,7 +2766,7 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) { - HLOGP(tslog.Debug, "recvmsg: KICK tsbpd() (buffer empty)"); + HLOGP(tslog.Debug, "recvmsg: SIGNAL TSBPD (buffer empty)"); tscond.notify_all(); } @@ -6033,7 +6055,7 @@ int CUDTGroup::checkLazySpawnLatencyThread() #else const string thname = "SRT:GLat"; #endif - if (!StartThread(m_RcvTsbPdThread, &CUDTGroup::tsbpd, this, thname)) + if (!StartThread(m_RcvTsbPdThread, CUDTGroup::tsbpd, this, thname)) return -1; } @@ -6052,15 +6074,13 @@ void* CUDTGroup::tsbpd(void* param) // NOTE: DO NOT LEAD TO EVER CANCEL THE THREAD!!! ScopedGroupKeeper gkeeper(self); - CUniqueSync recvdata_cc(self->m_RcvDataLock, self->m_RcvDataCond); - CSync tsbpd_cc(self->m_RcvTsbPdCond, recvdata_cc.locker()); + CUniqueSync recvdata_lcc(self->m_RcvDataLock, self->m_RcvDataCond); + CSync tsbpd_cc(self->m_RcvTsbPdCond, recvdata_lcc.locker()); - self->m_bWakeOnRecv = true; + self->m_bTsbpdWaitForNewPacket = true; + HLOGC(gmlog.Debug, log << "grp/TSBPD: START"); while (!self->m_bClosing) { - steady_clock::time_point tsNextDelivery; // Next packet delivery time - bool rxready = false; - enterCS(self->m_RcvBufferLock); const steady_clock::time_point tnow = steady_clock::now(); @@ -6068,7 +6088,14 @@ void* CUDTGroup::tsbpd(void* param) const srt::CRcvBufferNew::PacketInfo info = self->m_pRcvBuffer->getFirstValidPacketInfo(); const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); - tsNextDelivery = info.tsbpd_time; + steady_clock::time_point tsNextDelivery = info.tsbpd_time; + bool rxready = false; + + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: packet check: T=" + << FormatTime(tsNextDelivery) << " diff-in-late=" + << FormatDuration(tnow - tsNextDelivery) + << " ready=" << is_time_to_deliver + << " ondrop=" << info.seq_gap); if (!self->m_bTLPktDrop) { @@ -6084,7 +6111,7 @@ void* CUDTGroup::tsbpd(void* param) const int64_t timediff_us = count_microseconds(tnow - info.tsbpd_time); #if ENABLE_HEAVY_LOGGING HLOGC(tslog.Debug, - log << self->CONID() << "tsbpd: DROPSEQ: up to seqno %" << CSeqNo::decseq(info.seqno) << " (" + log << self->CONID() << "grp/tsbpd: DROPSEQ: up to seqno %" << CSeqNo::decseq(info.seqno) << " (" << iDropCnt << " packets) playable at " << FormatTime(info.tsbpd_time) << " delayed " << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') << (timediff_us % 1000) << " ms"); @@ -6103,7 +6130,7 @@ void* CUDTGroup::tsbpd(void* param) if (rxready) { HLOGC(tslog.Debug, - log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << info.seqno << " (belated " + log << self->CONID() << "grp/tsbpd: PLAYING PACKET seq=" << info.seqno << " (belated " << (count_milliseconds(steady_clock::now() - info.tsbpd_time)) << "ms)"); /* * There are packets ready to be delivered @@ -6111,7 +6138,8 @@ void* CUDTGroup::tsbpd(void* param) */ if (self->m_bSynRecving) { - recvdata_cc.notify_one(); + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: SIGNAL blocking recv()"); + recvdata_lcc.notify_one(); } /* * Set EPOLL_IN to wakeup any thread waiting on epoll @@ -6120,7 +6148,26 @@ void* CUDTGroup::tsbpd(void* param) CGlobEvent::triggerEvent(); tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } + else + { + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: NEXT PACKET: " + << (info.tsbpd_time == time_point() ? "NOT AVAILABLE" : FormatTime(info.tsbpd_time)) + << " vs. now=" << FormatTime(tnow)); + } + SRT_ATR_UNUSED bool got_signal = true; + + // None should be true in case when waiting for the next time. + // If there is a ready packet, but only to be extracted in some time, + // then sleep until this time and then retry triggering. + self->m_bTsbpdWaitForNewPacket = false; + self->m_bTsbpdWaitForExtraction = false; + + // NOTE: if (rxready) then tsNextDelivery == 0. So this branch is for a situation + // when: + // - no packet is currently READY for delivery + // - but there is a packet candidate ready soon. + // So you have to sleep until it's ready and then trigger read-readiness. if (!is_zero(tsNextDelivery)) { IF_HEAVY_LOGGING(const steady_clock::duration timediff = tsNextDelivery - tnow); @@ -6128,12 +6175,11 @@ void* CUDTGroup::tsbpd(void* param) * Buffer at head of queue is not ready to play. * Schedule wakeup when it will be. */ - self->m_bWakeOnRecv = false; HLOGC(tslog.Debug, - log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << info.seqno - << " T=" << FormatTime(tsNextDelivery) << " - waiting " << count_milliseconds(timediff) << "ms"); + log << self->CONID() << "grp/tsbpd: FUTURE PACKET seq=" << info.seqno + << " T=" << FormatTime(tsNextDelivery) << " - waiting " << count_milliseconds(timediff) << "ms up to " << FormatTime(tsNextDelivery)); THREAD_PAUSED(); - tsbpd_cc.wait_until(tsNextDelivery); + got_signal = tsbpd_cc.wait_until(tsNextDelivery); THREAD_RESUMED(); } else @@ -6146,20 +6192,33 @@ void* CUDTGroup::tsbpd(void* param) * Block until woken up by one of the following event: * - All ready-to-play packets have been pulled and EPOLL_IN cleared (then loop to block until next pkt time * if any) - * - New buffers ACKed + * - New packet arrived * - Closing the connection */ - HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: no data, scheduling wakeup at ack"); - self->m_bWakeOnRecv = true; + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: " << (rxready ? "expecting user's packet retrieval" : "no data to deliver") << ", scheduling wakeup on reception"); + + // If there was rxready, then epoll readiness was set, and recvdata_lcc was triggered + // - so it should remain sleeping until the user's thread has extracted EVERY ready packet and turned epoll back to not-ready. + // Otherwise the situation was that there's no ready packet at all. + // - so it should remain sleeping until a new packet arrives and it is potentially extractable. + if (rxready) + { + self->m_bTsbpdWaitForExtraction = true; + } + else + { + self->m_bTsbpdWaitForNewPacket = true; + } THREAD_PAUSED(); tsbpd_cc.wait(); THREAD_RESUMED(); } - HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP!!!"); + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: WAKE UP on " << (got_signal ? "signal" : "timeout") + << "; now=" << FormatTime(steady_clock::now())); } THREAD_EXIT(); - HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: EXITING"); + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: EXITING"); return NULL; } diff --git a/srtcore/group.h b/srtcore/group.h index a12694f52..168614c46 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -115,7 +115,7 @@ class CUDTGroup ~CUDTGroup(); #if ENABLE_NEW_RCVBUFFER - void createBuffers(int32_t isn); + void createBuffers(int32_t isn, const time_point& tsbpd_start_time); #endif SocketData* add(SocketData data); @@ -683,8 +683,10 @@ class CUDTGroup bool m_bTsbPd; bool m_bTLPktDrop; int64_t m_iTsbPdDelay_us; +#if !ENABLE_NEW_RCVBUFFER int m_RcvEID; class CEPollDesc* m_RcvEpolld; +#endif int m_SndEID; class CEPollDesc* m_SndEpolld; @@ -716,13 +718,13 @@ class CUDTGroup ReadPos* checkPacketAhead(); - void recv_CollectAliveAndBroken(std::vector& w_alive, std::set& w_broken); - #if ENABLE_NEW_RCVBUFFER sync::atomic m_RcvLastSeqNo; #else + void recv_CollectAliveAndBroken(std::vector& w_alive, std::set& w_broken); + /// The function polls alive member sockets and retrieves a list of read-ready. /// [acquires lock for CUDT::uglobal()->m_GlobControlLock] /// [[using locked(m_GroupLock)]] temporally unlocks-locks internally @@ -765,7 +767,8 @@ class CUDTGroup sync::Mutex m_RcvDataLock; #if ENABLE_NEW_RCVBUFFER sync::Condition m_RcvTsbPdCond; - sync::atomic m_bWakeOnRecv; + sync::atomic m_bTsbpdWaitForNewPacket; // TSBPD forever-wait should be signaled by new packet reception + sync::atomic m_bTsbpdWaitForExtraction;// TSBPD forever-wait should be signaled by extracting the last ready packet mutable sync::Mutex m_RcvBufferLock; // Protects the state of the m_pRcvBuffer #endif @@ -966,7 +969,7 @@ class CUDTGroup SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRT_GROUP_TYPE, type, m_type); SRTU_PROPERTY_RW_CHAIN(CUDTGroup, int32_t, currentSchedSequence, m_iLastSchedSeqNo); SRTU_PROPERTY_RRW(std::set&, epollset, m_sPollID); - SRTU_PROPERTY_RW_CHAIN(CUDTGroup, int64_t, latency, m_iTsbPdDelay_us); + SRTU_PROPERTY_RW_CHAIN(CUDTGroup, int64_t, latency_us, m_iTsbPdDelay_us); SRTU_PROPERTY_RO(bool, closing, m_bClosing); }; diff --git a/srtcore/handshake.h b/srtcore/handshake.h index 93a351f39..87c9d5761 100644 --- a/srtcore/handshake.h +++ b/srtcore/handshake.h @@ -311,6 +311,8 @@ class CHandShake // Applicable only when m_iVersion == HS_VERSION_SRT1 int32_t flags() { return m_iType; } + bool v5() { return m_iVersion > 4; } + public: int32_t m_iVersion; // UDT version (HS_VERSION_* symbols) int32_t m_iType; // UDT4: socket type (only UDT_DGRAM is valid); SRT1: extension flags diff --git a/srtcore/sync.h b/srtcore/sync.h index 5d62536eb..840fc47c2 100644 --- a/srtcore/sync.h +++ b/srtcore/sync.h @@ -769,7 +769,7 @@ struct DurationUnitName template inline std::string FormatDuration(const steady_clock::duration& dur) { - return Sprint(DurationUnitName::count(dur)) + DurationUnitName::name(); + return Sprint(std::fixed, DurationUnitName::count(dur)) + DurationUnitName::name(); } inline std::string FormatDuration(const steady_clock::duration& dur) diff --git a/srtcore/tsbpd_time.cpp b/srtcore/tsbpd_time.cpp index 046c90b74..162fc7ac7 100644 --- a/srtcore/tsbpd_time.cpp +++ b/srtcore/tsbpd_time.cpp @@ -220,7 +220,16 @@ CTsbpdTime::time_point CTsbpdTime::getTsbPdTimeBase(uint32_t timestamp_us) const CTsbpdTime::time_point CTsbpdTime::getPktTsbPdTime(uint32_t usPktTimestamp) const { - return getPktTsbPdBaseTime(usPktTimestamp) + m_tdTsbPdDelay + microseconds_from(m_DriftTracer.drift()); + time_point value = getPktTsbPdBaseTime(usPktTimestamp) + m_tdTsbPdDelay + microseconds_from(m_DriftTracer.drift()); + + /* + HLOGC(brlog.Debug, log << "getPktTsbPdTime:" + << " BASE=" << FormatTime(m_tsTsbPdTimeBase) + << " TS=" << usPktTimestamp << "us, lat=" << FormatDuration(m_tdTsbPdDelay) + << " DRF=" << m_DriftTracer.drift() << "us = " << FormatTime(value)); + */ + + return value; } CTsbpdTime::time_point CTsbpdTime::getPktTsbPdBaseTime(uint32_t usPktTimestamp) const diff --git a/srtcore/utilities.h b/srtcore/utilities.h index 8c333d5e7..ec5f5cfe7 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -666,6 +666,15 @@ inline std::string Sprint(const Arg1& arg) return sout.str(); } +// Ok, let it be 2-arg, in case when a manipulator is needed +template +inline std::string Sprint(const Arg1& arg1, const Arg2& arg2) +{ + std::ostringstream sout; + sout << arg1 << arg2; + return sout.str(); +} + template inline std::string Printable(const Container& in) { From b5d95d227b2980054b2e3580e225d430278bf5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Wed, 31 Aug 2022 09:17:32 +0200 Subject: [PATCH 08/62] Added a workaround to read-ready check for a group-member socket --- srtcore/core.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 945575e14..41243a653 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -6849,8 +6849,16 @@ bool srt::CUDT::isRcvBufferReady() const { #if ENABLE_NEW_BONDING // In "new bonding" (new rcvbuf + bonding) - // this function should never be called for member sockets. + // the group member socket is never read-ready. This function is called + // from various parts, not always exactly necessary, but it's + // too complicated to untangle it without refaxing the epoll system. + + // Make a crash in debug mode so that it can be easily detected, + // but simply ignore the problm in release mode. SRT_ASSERT(m_parent->m_GroupOf == NULL); + + if (m_parent->m_GroupOf) + return false; #endif ScopedLock lck(m_RcvBufferLock); #if ENABLE_NEW_RCVBUFFER From f03b09e53d21bf659df168b5580ab0cd8077bb8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 2 Sep 2022 13:17:43 +0200 Subject: [PATCH 09/62] Added specific algorithm for tracking group loss for balancing groups --- srtcore/api.cpp | 2 +- srtcore/buffer_rcv.cpp | 106 +++++++++++++++++++++++++++++ srtcore/buffer_rcv.h | 3 + srtcore/common.h | 20 ++++++ srtcore/core.cpp | 38 ++++++++--- srtcore/group.cpp | 146 +++++++++++++++++++++++++++++++++++++++- srtcore/group.h | 7 +- srtcore/queue.cpp | 7 +- srtcore/queue.h | 7 +- srtcore/utilities.h | 7 ++ test/test_buffer.cpp | 2 +- test/test_unitqueue.cpp | 6 +- 12 files changed, 326 insertions(+), 25 deletions(-) diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 417020a54..c9dd0ccf3 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -3025,7 +3025,7 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& addr, const U m.m_pSndQueue = new CSndQueue; m.m_pSndQueue->init(m.m_pChannel, m.m_pTimer); m.m_pRcvQueue = new CRcvQueue; - m.m_pRcvQueue->init(128, s->core().maxPayloadSize(), m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer); + m.m_pRcvQueue->init(128, s->core().maxPayloadSize(), m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer, s->m_SocketID); // Rewrite the port here, as it might be only known upon return // from CChannel::open. diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 2550ce919..82ab321bb 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -1374,4 +1374,110 @@ void CRcvBufferNew::updRcvAvgDataSize(const steady_clock::time_point& now) m_mavg.update(now, pkts, bytes, timespan_ms); } +int32_t CRcvBufferNew::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) +{ + int offset = CSeqNo::seqoff(m_iStartSeqNo, fromseq); + + // Check if it's still inside the buffer + if (offset < 0 || offset >= m_iMaxPosOff) + return SRT_SEQNO_NONE; + + // Start position + int pos = incPos(m_iStartPos, offset); + + // Ok; likely we should stand at the m_iEndPos position. + // If this given position is earlier than this, then + // m_iEnd stands on the first loss, unless it's equal + // to the position pointed by m_iMaxPosOff. + + int32_t ret_seq = SRT_SEQNO_NONE; + int ret_off = m_iMaxPosOff; + + int end_off = offPos(m_iStartPos, m_iEndPos); + if (pos < end_off) + { + // If m_iEndPos has such a value, then there are + // no loss packets at all. + if (end_off != m_iMaxPosOff) + { + ret_seq = CSeqNo::incseq(m_iStartSeqNo, end_off); + ret_off = end_off; + } + } + else + { + // Could be strange, but just as the caller wishes: + // find the first loss since this point on + // You can't rely on m_iEndPos, you are beyond that now. + // So simply find the next hole. + + // REUSE offset as a control variable + for (; offset < m_iMaxPosOff; ++offset) + { + int pos = incPos(m_iStartPos, offset); + if (m_entries[pos].status == EntryState_Empty) + { + ret_off = offset; + ret_seq = CSeqNo::incseq(m_iStartSeqNo, offset); + break; + } + } + } + + // If found no loss, just return this value and do not + // rewrite nor look for anything. + + // Also no need to search anything if only the beginning was + // being looked for. + if (ret_seq == SRT_SEQNO_NONE || !pw_end) + return ret_seq; + + // We want also the end range, so continue from where you + // stopped. + + for (int off = ret_off; off < m_iMaxPosOff; ++off) + { + int pos = incPos(m_iStartPos, off); + if (m_entries[pos].status != EntryState_Empty) + { + *pw_end = CSeqNo::incseq(m_iStartSeqNo, off); + return ret_seq; + } + } + + // Fallback - this should be impossible, so issue a log. + LOGC(rbuflog.Error, log << "IPE: empty cell pos=" << pos << " %" << CSeqNo::incseq(m_iStartSeqNo, ret_off) << " not followed by any valid cell"); + + // Return this in the last resort - this could only be a situation when + // a packet has somehow disappeared, but it contains empty cells up to the + // end of buffer occupied range. This shouldn't be possible at all because + // there must be a valid packet at least at the last occupied cell. + return SRT_SEQNO_NONE; +} + +void CRcvBufferNew::getUnitSeriesInfo(int32_t fromseq, size_t maxsize, std::vector& w_sources) +{ + const int offset = CSeqNo::seqoff(m_iStartSeqNo, fromseq); + + // Check if it's still inside the buffer + if (offset < 0 || offset >= m_iMaxPosOff) + return; + + // All you need to do is to check if there's a valid packet + // at given position + size_t pass = 0; + for (int off = offset; off < m_iMaxPosOff; ++off) + { + int pos = incPos(m_iStartPos, off); + if (m_entries[pos].pUnit) + { + w_sources.push_back(m_entries[pos].pUnit->m_pParentQueue->ownerID()); + ++pass; + if (pass == maxsize) + break; + } + } +} + + } // namespace srt diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 15c3eb5ff..03eb8ab84 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -371,6 +371,9 @@ class CRcvBufferNew /// @note CSeqNo::seqoff(first, second) is 0 if nothing to read. std::pair getAvailablePacketsRange() const; + int32_t getFirstLossSeq(int32_t fromseq, int32_t* opt_end = NULL); + void getUnitSeriesInfo(int32_t fromseq, size_t maxsize, std::vector& w_sources); + bool empty() const { return (m_iMaxPosOff == 0); diff --git a/srtcore/common.h b/srtcore/common.h index 227a91861..c8de517c0 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -1416,6 +1416,26 @@ inline std::string SrtVersionString(int version) bool SrtParseConfig(std::string s, SrtConfig& w_config); + +inline std::string FormatLossArray(const std::vector< std::pair >& lra) +{ + std::ostringstream os; + + os << "[ "; + for (std::vector< std::pair >::const_iterator i = lra.begin(); i != lra.end(); ++i) + { + int len = CSeqNo::seqoff(i->first, i->second); + os << "%" << i->first; + if (len > 1) + os << "+" << len; + os << " "; + } + + os << "]"; + return os.str(); +} + + } // namespace srt #endif diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 41243a653..39914bd64 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -10405,11 +10405,11 @@ int srt::CUDT::processData(CUnit* in_unit) if (offset < 0) { IF_HEAVY_LOGGING(exc_type = "BELATED"); + enterCS(m_StatsLock); const double bltime = (double) CountIIR( uint64_t(m_stats.traceBelatedTime) * 1000, count_microseconds(steady_clock::now() - pts), 0.2); - enterCS(m_StatsLock); m_stats.traceBelatedTime = bltime / 1000.0; m_stats.rcvr.recvdBelated.count(rpkt.getLength()); leaveCS(m_StatsLock); @@ -10430,6 +10430,11 @@ int srt::CUDT::processData(CUnit* in_unit) CRcvBufferNew::InsertInfo info = gkeeper.group->addDataUnit(u); if (info.result == CRcvBufferNew::InsertInfo::DISCREPANCY) { + // XXX PROBABLY the new receiver buffer can give the possibility + // of completely resetting itself at the moment when this happens, + // so closing may be not necessary in case of TLPKTDROP, but instead + // the whole buffer will be dropped and it will start over from the + // newly incoming sequence number. if (m_bGroupTsbPd && info.avail_range == 0) { LOGC(qrlog.Error, log << CONID() << @@ -10602,15 +10607,19 @@ int srt::CUDT::processData(CUnit* in_unit) // Lame way to make an optional variable. bool have_loss = false; - typename loss_seqs_t::value_type this_loss; #if ENABLE_NEW_RCVBUFFER if (gkeeper.group) { // This should take out assymmetric losses, and leave only // valid ones. This also reports the losses to the group. - have_loss = gkeeper.group->checkPacketArrivalLoss(rpkt, (this_loss)); + have_loss = gkeeper.group->checkPacketArrivalLoss(rpkt, (srt_loss_seqs)); handled = true; + + if (have_loss) + { + HLOGC(qrlog.Debug, log << "grp/LOSS DETECTED: " << FormatLossArray(srt_loss_seqs)); + } } #endif @@ -10619,8 +10628,8 @@ int srt::CUDT::processData(CUnit* in_unit) int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); - this_loss = make_pair(seqlo, seqhi); have_loss = true; + srt_loss_seqs.push_back(make_pair(seqlo, seqhi)); } if (initial_loss_ttl && have_loss) @@ -10628,15 +10637,17 @@ int srt::CUDT::processData(CUnit* in_unit) // pack loss list for (possibly belated) NAK // The LOSSREPORT will be sent in a while. - m_FreshLoss.push_back(CRcvFreshLoss(this_loss.first, this_loss.second, initial_loss_ttl)); - HLOGC(qrlog.Debug, - log << "FreshLoss: added sequences: %(" << this_loss.first << "-" << this_loss.second - << ") tolerance: " << initial_loss_ttl); reorder_prevent_lossreport = true; - } - if (have_loss) - srt_loss_seqs.push_back(this_loss); + ScopedLock lg(m_RcvLossLock); + for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i) + { + m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); + HLOGC(qrlog.Debug, + log << "FreshLoss: added sequences: %(" << i->first << "-" << i->second + << ") tolerance: " << initial_loss_ttl); + } + } } // Update the current largest sequence number that has been received. @@ -10738,6 +10749,10 @@ int srt::CUDT::processData(CUnit* in_unit) sendLossReport(srt_loss_seqs); } + // This should not be required with the new receiver buffer because + // signalling happens on every packet reception, if it has changed the + // earliest packet position. +#if !ENABLE_NEW_RCVBUFFER if (m_bTsbPd) { HLOGC(qrlog.Debug, log << "loss: signaling TSBPD cond"); @@ -10747,6 +10762,7 @@ int srt::CUDT::processData(CUnit* in_unit) { HLOGC(qrlog.Debug, log << "loss: socket is not TSBPD, not signaling"); } +#endif } // Separately report loss records of those reported by a filter. diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 96210fa61..9826a4697 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -255,8 +255,11 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) , m_GroupID(-1) , m_PeerGroupID(-1) , m_type(gtype) +#if !ENABLE_NEW_RCVBUFFER , m_listener() +#endif , m_iBusy() + , m_iRcvPossibleLossSeq(SRT_SEQNO_NONE) , m_iSndOldestMsgNo(SRT_MSGNO_NONE) , m_iSndAckedMsgNo(SRT_MSGNO_NONE) , m_uOPT_MinStabilityTimeout_us(1000 * CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS) @@ -1074,7 +1077,7 @@ int CUDTGroup::rcvDropTooLateUpTo(int seqno) } -bool CUDTGroup::checkPacketArrivalLoss(const CPacket& rpkt, typename CUDT::loss_seqs_t::value_type& w_loss) +bool CUDTGroup::checkPacketArrivalLoss(const CPacket& rpkt, CUDT::loss_seqs_t& w_losses) { // This is called when the packet was added to the buffer and this // adding was successful. Here we need to: @@ -1088,13 +1091,25 @@ bool CUDTGroup::checkPacketArrivalLoss(const CPacket& rpkt, typename CUDT::loss_ bool have = false; - if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_RcvLastSeqNo)) > 0) + // m_RcvLastSeqNo is atomic, so no need to protect it, + // but it's also being modified using R-M-W method, and + // this can potentially be interleft. + ScopedLock gl (m_GroupLock); + + // For balancing groups, use some more complicated mechanism. + if (type() == SRT_GTYPE_BALANCING) + { + have = checkBalancingLoss(rpkt, (w_losses)); + } + else if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_RcvLastSeqNo)) > 0) { int32_t seqlo = CSeqNo::incseq(m_RcvLastSeqNo); int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); - w_loss = make_pair(seqlo, seqhi); + w_losses.push_back(make_pair(seqlo, seqhi)); have = true; + HLOGC(gmlog.Debug, log << "grp:checkPacketArrivalLoss: loss detected: %(" + << seqlo << " - " << seqhi << ")"); } if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_RcvLastSeqNo) > 0) @@ -1104,6 +1119,131 @@ bool CUDTGroup::checkPacketArrivalLoss(const CPacket& rpkt, typename CUDT::loss_ return have; } + +struct FFringeGreaterThan +{ + size_t baseval; + FFringeGreaterThan(size_t b): baseval(b) {} + + template + bool operator()(const pair& val) + { + return val.second > baseval; + } +}; + +bool CUDTGroup::checkBalancingLoss(const CPacket& pkt, CUDT::loss_seqs_t& w_losses) +{ + // This is done in case of every incoming packet. + + if (pkt.getSeqNo() == m_iRcvPossibleLossSeq) + { + // This seals the exact loss position. + // The returned value can be also NONE, which clears out the loss information. + m_iRcvPossibleLossSeq = m_pRcvBuffer->getFirstLossSeq(m_iRcvPossibleLossSeq); + + HLOGC(gmlog.Debug, log << "grp:checkBalancingLoss: %" << pkt.getSeqNo() << " SEALS A LOSS, shift to %" << m_iRcvPossibleLossSeq); + return false; + } + + // We state that this is the oldest possible loss sequence; just formally check + int cmp = CSeqNo::seqcmp(pkt.m_iSeqNo, m_RcvLastSeqNo); + if (cmp < 0) + { + HLOGC(gmlog.Debug, log << "grp:checkBalancingLoss: %" << pkt.getSeqNo() << " IN THE PAST"); + return false; + } + + // We need to check first, if we ALREADY have some older loss candidate, + // and if so, if the condition for having it "eclipsed" is satisfied. + + bool found_reportable_losses = false, more_losses = false; + + while (m_iRcvPossibleLossSeq != SRT_SEQNO_NONE) + { + // We do have a recorded loss before. Get unit information. + vector followers; + m_pRcvBuffer->getUnitSeriesInfo(m_iRcvPossibleLossSeq, m_Group.size(), (followers)); + + // The "eclipse" condition is one of two: + // + // When the loss (even if divided by other losses) is followed by some + // number of packets, among which: + // + // 1. There is at least one packet from every link. + // 2. There are at least two packets coming from one of the links. + + HLOGC(gmlog.Debug, log << "grp:checkBalancingLoss: existng %" << m_iRcvPossibleLossSeq << " followed by: " + << Printable(followers)); + + map nums; + FringeValues(followers, (nums)); + + if (nums.size() >= m_Group.size() // 1 + || find_if(nums.begin(), nums.end(), FFringeGreaterThan(1)) != nums.end()) + { + // Extract the whole first loss + typename CUDT::loss_seqs_t::value_type loss; + loss.first = m_pRcvBuffer->getFirstLossSeq(m_iRcvPossibleLossSeq, (&loss.second)); + w_losses.push_back(loss); + + found_reportable_losses = true; + + // Save the next found loss + m_iRcvPossibleLossSeq = m_pRcvBuffer->getFirstLossSeq(CSeqNo::incseq(loss.second)); + + HLOGC(gmlog.Debug, log << "... qualified as loss: %(" << loss.first << " - " << loss.second + << "), next loss: %" << m_iRcvPossibleLossSeq); + + if (m_iRcvPossibleLossSeq == SRT_SEQNO_NONE) + { + // We extracted all losses + more_losses = false; + break; + } + + // Found at least one reportable loss + more_losses = true; + continue; + } + + break; + } + + // found_reportable_losses = at least one of the so far POTENTIAL loss was confirmed as ACTUAL loss and we report it. + // more_losses = not all seen losses have been extracted (so don't try to register a new POTENTIAL loss) + + // In case when the above procedure didn't set m_iRcvPossibleLossSeq, + // check now the CURRENT arrival if it doesn't create a new loss. + + // HERE: if !more_losses, then m_iRcvPossibleLossSeq == SRT_SEQNO_NONE. + // This condition may change it or leave as is. + + int32_t next_seqno = CSeqNo::incseq(m_RcvLastSeqNo); + if (!more_losses && CSeqNo::seqcmp(pkt.m_iSeqNo, next_seqno) > 0) + { + // NOTE: in case when you have (at least temporarily) only one link, + // then you have to do the same as with a general case. The above loop + // had to be performed anyway, but this only touches upon any earlier losses. + // In this case if we have one link only, do not notify it for the next time, + // but report it directly instead. + if (m_Group.size() == 1) + { + typename CUDT::loss_seqs_t::value_type loss = make_pair(next_seqno, CSeqNo::decseq(pkt.m_iSeqNo)); + w_losses.push_back(loss); + HLOGC(gmlog.Debug, log << "grp:checkBalancingLoss: incom %" << pkt.m_iSeqNo << " jumps over expected %" << next_seqno + << " - with 1 link only, just reporting"); + return true; + } + + HLOGC(gmlog.Debug, log << "grp:checkBalancingLoss: incom %" << pkt.m_iSeqNo << " jumps over expected %" << next_seqno + << " - setting up as loss candidate"); + m_iRcvPossibleLossSeq = next_seqno; + } + + return found_reportable_losses; +} + #endif void CUDTGroup::close() diff --git a/srtcore/group.h b/srtcore/group.h index 168614c46..a53c17b65 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -466,7 +466,9 @@ class CUDTGroup }; GroupContainer m_Group; SRT_GROUP_TYPE m_type; +#if !ENABLE_NEW_RCVBUFFER CUDTSocket* m_listener; // A "group" can only have one listener. XXX unsure what this is for actually +#endif srt::sync::atomic m_iBusy; CallbackHolder m_cbConnectHook; void installConnectHook(srt_connect_callback_fn* hook, void* opaq) @@ -663,6 +665,8 @@ class CUDTGroup }; friend ScopedGroupKeeper; + int32_t m_iRcvPossibleLossSeq; + #endif // Fields required for SRT_GTYPE_BACKUP groups. @@ -905,7 +909,8 @@ class CUDTGroup #if ENABLE_NEW_RCVBUFFER int checkLazySpawnLatencyThread(); CRcvBufferNew::InsertInfo addDataUnit(CUnit* u); - bool checkPacketArrivalLoss(const CPacket& rpkt, typename CUDT::loss_seqs_t::value_type&); + bool checkPacketArrivalLoss(const CPacket& rpkt, CUDT::loss_seqs_t&); + bool checkBalancingLoss(const CPacket& rpkt, CUDT::loss_seqs_t&); int rcvDropTooLateUpTo(int seqno); void addGroupDriftSample(uint32_t timestamp, const time_point& tsArrival, int rtt); #endif diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index cd445493e..93f8362af 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -65,10 +65,11 @@ using namespace std; using namespace srt::sync; using namespace srt_logging; -srt::CUnitQueue::CUnitQueue(int initNumUnits, int mss) +srt::CUnitQueue::CUnitQueue(int initNumUnits, int mss, UDPSOCKET owner) : m_iNumTaken(0) , m_iMSS(mss) , m_iBlockSize(initNumUnits) + , m_OwnerID(owner) { CQEntry* tempq = allocateEntry(m_iBlockSize, m_iMSS); @@ -1124,13 +1125,13 @@ srt::CRcvQueue::~CRcvQueue() srt::sync::atomic srt::CRcvQueue::m_counter(0); #endif -void srt::CRcvQueue::init(int qsize, size_t payload, int version, int hsize, CChannel* cc, CTimer* t) +void srt::CRcvQueue::init(int qsize, size_t payload, int version, int hsize, CChannel* cc, CTimer* t, SRTSOCKET owner) { m_iIPversion = version; m_szPayloadSize = payload; SRT_ASSERT(m_pUnitQueue == NULL); - m_pUnitQueue = new CUnitQueue(qsize, (int)payload); + m_pUnitQueue = new CUnitQueue(qsize, (int)payload, owner); m_pHash = new CHash; m_pHash->init(hsize); diff --git a/srtcore/queue.h b/srtcore/queue.h index 87b8ab477..9b1adca19 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -93,13 +93,15 @@ class CUnitQueue /// @param mss Initial number of units to allocate. /// @param mss Maximum segment size meaning the size of each unit. /// @throws CUDTException SRT_ENOBUF. - CUnitQueue(int initNumUnits, int mss); + CUnitQueue(int initNumUnits, int mss, SRTSOCKET owner); ~CUnitQueue(); public: int capacity() const { return m_iSize; } int size() const { return m_iSize - m_iNumTaken; } + SRTSOCKET ownerID() const { return m_OwnerID; } + public: /// @brief Find an available unit for incoming packet. Allocate new units if 90% or more are in use. /// @note This function is not thread-safe. Currently only CRcvQueue::worker thread calls it, thus @@ -141,6 +143,7 @@ class CUnitQueue sync::atomic m_iNumTaken; // total number of valid (occupied) packets in the queue const int m_iMSS; // unit buffer size const int m_iBlockSize; // Number of units in each CQEntry. + SRTSOCKET m_OwnerID; private: CUnitQueue(const CUnitQueue&); @@ -509,7 +512,7 @@ class CRcvQueue /// @param [in] hsize hash table size /// @param [in] c UDP channel to be associated to the queue /// @param [in] t timer - void init(int size, size_t payload, int version, int hsize, CChannel* c, sync::CTimer* t); + void init(int size, size_t payload, int version, int hsize, CChannel* c, sync::CTimer* t, SRTSOCKET owner); /// Read a packet for a specific UDT socket id. /// @param [in] id Socket ID diff --git a/srtcore/utilities.h b/srtcore/utilities.h index ec5f5cfe7..0a66175e5 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -763,6 +763,13 @@ inline std::pair Tie(Type1& var1, Type2& var2) return std::pair(var1, var2); } +template +inline void FringeValues(const Container& from, std::map& out) +{ + for (typename Container::const_iterator i = from.begin(); i != from.end(); ++i) + ++out[*i]; +} + template struct CallbackHolder { diff --git a/test/test_buffer.cpp b/test/test_buffer.cpp index 35d93f7b5..1f714adc8 100644 --- a/test/test_buffer.cpp +++ b/test/test_buffer.cpp @@ -27,7 +27,7 @@ class CRcvBufferReadMsg void SetUp() override { // make_unique is unfortunatelly C++14 - m_unit_queue.reset(new CUnitQueue(m_buff_size_pkts, 1500)); + m_unit_queue.reset(new CUnitQueue(m_buff_size_pkts, 1500, 1 /*stub socket ID*/)); ASSERT_NE(m_unit_queue.get(), nullptr); #if ENABLE_NEW_RCVBUFFER diff --git a/test/test_unitqueue.cpp b/test/test_unitqueue.cpp index 255aabe47..db6523d0e 100644 --- a/test/test_unitqueue.cpp +++ b/test/test_unitqueue.cpp @@ -16,7 +16,7 @@ using namespace srt; TEST(CUnitQueue, Increase) { const int buffer_size_pkts = 4; - CUnitQueue unit_queue(buffer_size_pkts, 1500); + CUnitQueue unit_queue(buffer_size_pkts, 1500, 1); vector taken_units; for (int i = 0; i < 5 * buffer_size_pkts; ++i) @@ -36,7 +36,7 @@ TEST(CUnitQueue, Increase) TEST(CUnitQueue, IncreaseAndFree) { const int buffer_size_pkts = 4; - CUnitQueue unit_queue(buffer_size_pkts, 1500); + CUnitQueue unit_queue(buffer_size_pkts, 1500, 1); CUnit* taken_unit = nullptr; for (int i = 0; i < 5 * buffer_size_pkts; ++i) @@ -60,7 +60,7 @@ TEST(CUnitQueue, IncreaseAndFree) TEST(CUnitQueue, IncreaseAndFreeGrouped) { const int buffer_size_pkts = 4; - CUnitQueue unit_queue(buffer_size_pkts, 1500); + CUnitQueue unit_queue(buffer_size_pkts, 1500, 1); vector taken_units; for (int i = 0; i < 5 * buffer_size_pkts; ++i) From 38510ab0d5fff2d4886f73ba4b496d4a40257a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 2 Sep 2022 15:05:15 +0200 Subject: [PATCH 10/62] Fixed broken release build --- srtcore/core.cpp | 7 +++---- srtcore/group.cpp | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 39914bd64..b64bbfe1b 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -10185,13 +10185,12 @@ int srt::CUDT::processData(CUnit* in_unit) #endif } -#if ENABLE_HEAVY_LOGGING + steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) + steady_clock::time_point pts = steady_clock::time_point() + tsbpddelay; - steady_clock::time_point pts; +#if ENABLE_HEAVY_LOGGING { - steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) - pts = steady_clock::time_point() + tsbpddelay; // It's easier to remove the latency factor from this value than to add a function // that exposes the details basing on which this value is calculated. diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 9826a4697..7937bfe2e 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -2229,7 +2229,7 @@ struct FLookupSocketWithEvent_LOCKED } }; -#if !ENABLE_HEAVY_LOGGING +#if !ENABLE_NEW_RCVBUFFER void CUDTGroup::recv_CollectAliveAndBroken(vector& alive, set& broken) { From 23dec40e5ab05078b56da56db248caa6d1382a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 2 Sep 2022 16:55:08 +0200 Subject: [PATCH 11/62] Fixed bug: wrong loss range found in the buffer. Added broadcast groups to balancing-mode loss report --- srtcore/buffer_rcv.cpp | 6 ++++-- srtcore/group.cpp | 42 +++++++++++++++++++----------------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 82ab321bb..b9cfa1d02 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -1435,12 +1435,14 @@ int32_t CRcvBufferNew::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) // We want also the end range, so continue from where you // stopped. - for (int off = ret_off; off < m_iMaxPosOff; ++off) + // Start from ret_off + 1 because we know already that ret_off + // points to an empty cell. + for (int off = ret_off + 1; off < m_iMaxPosOff; ++off) { int pos = incPos(m_iStartPos, off); if (m_entries[pos].status != EntryState_Empty) { - *pw_end = CSeqNo::incseq(m_iStartSeqNo, off); + *pw_end = CSeqNo::incseq(m_iStartSeqNo, off - 1); return ret_seq; } } diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 7937bfe2e..51200c1a7 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -1030,36 +1030,32 @@ CRcvBufferNew::InsertInfo CUDTGroup::addDataUnit(CUnit* u) return info; } - +// [[using locked(m_RcvBufferLock)]] int CUDTGroup::rcvDropTooLateUpTo(int seqno) { int iDropCnt = 0; + // Nothing to drop from an empty buffer. + // Required to check first to secure size()-1 expression. + if (!m_pRcvBuffer->empty()) { - ScopedLock lk (m_RcvBufferLock); - - // Nothing to drop from an empty buffer. - // Required to check first to secure size()-1 expression. - if (!m_pRcvBuffer->empty()) - { - // Make sure that it would not drop over m_iRcvCurrSeqNo, which may break senders. - int32_t last_seq = CSeqNo::incseq(m_pRcvBuffer->getStartSeqNo(), m_pRcvBuffer->size() - 1); - if (CSeqNo::seqcmp(seqno, last_seq) > 0) - seqno = last_seq; + // Make sure that it would not drop over m_iRcvCurrSeqNo, which may break senders. + int32_t last_seq = CSeqNo::incseq(m_pRcvBuffer->getStartSeqNo(), m_pRcvBuffer->size() - 1); + if (CSeqNo::seqcmp(seqno, last_seq) > 0) + seqno = last_seq; - iDropCnt = m_pRcvBuffer->dropUpTo(seqno); + iDropCnt = m_pRcvBuffer->dropUpTo(seqno); - /* not sure how to stats. - if (iDropCnt > 0) - { - enterCS(m_StatsLock); - // Estimate dropped bytes from average payload size. - const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); - m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * avgpayloadsz, (uint32_t) iDropCnt)); - leaveCS(m_StatsLock); - } - */ + /* not sure how to stats. + if (iDropCnt > 0) + { + enterCS(m_StatsLock); + // Estimate dropped bytes from average payload size. + const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); + m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * avgpayloadsz, (uint32_t) iDropCnt)); + leaveCS(m_StatsLock); } + */ } // Update every member's loss lists @@ -1097,7 +1093,7 @@ bool CUDTGroup::checkPacketArrivalLoss(const CPacket& rpkt, CUDT::loss_seqs_t& w ScopedLock gl (m_GroupLock); // For balancing groups, use some more complicated mechanism. - if (type() == SRT_GTYPE_BALANCING) + if (type() == SRT_GTYPE_BALANCING || type() == SRT_GTYPE_BROADCAST) { have = checkBalancingLoss(rpkt, (w_losses)); } From e7391339d212f156eee1f88ccfbd873f904e5aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 22 Sep 2022 09:54:33 +0200 Subject: [PATCH 12/62] Added common group losses handling --- srtcore/core.cpp | 337 +++++++++++++++++++++++++++++------------ srtcore/core.h | 4 +- srtcore/group.cpp | 181 +++++++++++++++++++--- srtcore/group.h | 13 +- srtcore/group_common.h | 12 +- srtcore/list.cpp | 262 ++++++++++++++++++++++++++++---- srtcore/list.h | 16 +- srtcore/utilities.h | 28 ++++ 8 files changed, 703 insertions(+), 150 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index b64bbfe1b..079693614 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -83,6 +83,9 @@ modified by #undef max #endif +// XXX For testing: use common loss list for also broadcast groups. +#define BROADCAST_COMMON_SND_LOSS 1 + using namespace std; using namespace srt; using namespace srt::sync; @@ -8315,10 +8318,57 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) { #if ENABLE_BONDING + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); // This is for the call of CSndBuffer::getMsgNoAt that returns // this value as a notfound-trap. int32_t msgno_at_last_acked_seq = SRT_MSGNO_CONTROL; - bool is_group = m_parent->m_GroupOf; +#endif + +#if ENABLE_NEW_BONDING + + using namespace any_op; + +#ifdef BROADCAST_COMMON_SND_LOSS + if (gkeeper.group && (EqualAny(gkeeper.group->type()), SRT_GTYPE_BALANCING, SRT_GTYPE_BROADCAST)) +#else + if (gkeeper.group && gkeeper.group->type() == SRT_GTYPE_BALANCING) +#endif + { + // XXX TEMPORARY added broadcast for testing. + + // For groups of type BALANCING we use the common loss handling. + + // XXX This method can be as well used for BROADCAST groups, if + // it is decided that a loss reported on whichever link should be + // then retransmitted using all links (while assymmetric losses + // will not be reported). + + // For BACKUP we stick to the individual per-socket + // loss handling as any sending on a different link than the current + // one should happen in case of possible breakup detection, so this + // way it doesn't make any sense to handle losses any other way than + // per socket, as usual. + gkeeper.group->updateSndLossListOnACK(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) + return; + + // update sending variables + m_iSndLastDataAck = ackdata_seqno; + + // 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(); + } + else #endif // Update sender's loss list and acknowledge packets in the sender's buffer @@ -8335,7 +8385,7 @@ void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) m_iSndLastDataAck = ackdata_seqno; #if ENABLE_BONDING - if (is_group) + if (gkeeper.group) { // Get offset-1 because 'offset' points actually to past-the-end // of the sender buffer. We have already checked that offset is @@ -8359,7 +8409,7 @@ void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) } #if ENABLE_BONDING - if (is_group) + if (gkeeper.group) { // m_RecvAckLock is ordered AFTER m_GlobControlLock, so this can only // be done now that m_RecvAckLock is unlocked. @@ -8765,18 +8815,30 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) // when logging is forcefully off. int32_t wrong_loss SRT_ATR_UNUSED = CSeqNo::m_iMaxSeqNo; - // protect packet retransmission { - ScopedLock ack_lock(m_RecvAckLock); - #if ENABLE_NEW_BONDING + // Keep group from disappearing in the meantime + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); typedef vector< pair > losses_t; losses_t losses; +#else + // This is off in the new-bonding because + // with new-bonding we'll be only collecting the losses + // in the temporary container and then add them all to + // the right loss list, and only there the locking will + // be necessary. + + // protect packet retransmission + ScopedLock ack_lock(m_RecvAckLock); #endif // decode loss list message and insert loss into the sender loss list for (int i = 0, n = (int)(ctrlpkt.getLength() / 4); i < n; ++i) { +#if !ENABLE_NEW_BONDING + int num = 0; // For stats +#endif + if (IsSet(losslist[i], LOSSDATA_SEQNO_RANGE_FIRST)) { // Then it's this is a specification with HI in a consecutive cell. @@ -8802,15 +8864,15 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) break; } - int num = 0; // IF losslist_lo %>= m_iSndLastAck if (CSeqNo::seqcmp(losslist_lo, m_iSndLastAck) >= 0) { HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << losslist_lo << " - " << losslist_hi << " to loss list"); - num = m_pSndLossList->insert(losslist_lo, losslist_hi); #if ENABLE_NEW_BONDING losses.push_back(make_pair(losslist_lo, losslist_hi)); +#else + num = m_pSndLossList->insert(losslist_lo, losslist_hi); #endif } // ELSE IF losslist_hi %>= m_iSndLastAck @@ -8824,9 +8886,10 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) // more important, so simply drop the part that predates ACK. HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << m_iSndLastAck << "[ACK] - " << losslist_hi << " to loss list"); - num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi); #if ENABLE_NEW_BONDING losses.push_back(make_pair(m_iSndLastAck.load(), losslist_hi)); +#else + num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi); #endif } else @@ -8858,9 +8921,6 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair)); } - enterCS(m_StatsLock); - m_stats.sndr.lost.count(num); - leaveCS(m_StatsLock); } else if (CSeqNo::seqcmp(losslist[i], m_iSndLastAck) >= 0) { @@ -8876,28 +8936,54 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "rcv LOSSREPORT: %" << losslist[i] << " (1 packet)"); - const int num = m_pSndLossList->insert(losslist[i], losslist[i]); #if ENABLE_NEW_BONDING - losses.push_back(make_pair(losslist[i], losslist[i])); + losses.push_back(make_pair(losslist[i], losslist[i])); +#else + num = m_pSndLossList->insert(losslist[i], losslist[i]); #endif - enterCS(m_StatsLock); - m_stats.sndr.lost.count(num); - leaveCS(m_StatsLock); } +#if !ENABLE_NEW_BONDING + enterCS(m_StatsLock); + m_stats.sndr.lost.count(num); + leaveCS(m_StatsLock); +#endif } + #if ENABLE_NEW_BONDING - if (m_parent->m_GroupMemberData) + int num = 0; + + using namespace any_op; + + if (gkeeper.group && m_parent->m_GroupMemberData + && (EqualAny(gkeeper.group->type()), SRT_GTYPE_BALANCING, SRT_GTYPE_BROADCAST)) { groups::SocketData* d = m_parent->m_GroupMemberData; - if (d->use_send_schedule) + // This will: + // 1. Add the loss to the group loss list + // 2. If use_send_schedule, it will also schedule these packets. + if (!m_parent->m_GroupOf->updateSendPacketLoss(d->use_send_schedule, losses)) { - if (!m_parent->m_GroupOf->updateSendPacketLoss(losses)) - { - LOGC(inlog.Error, log << CONID() << "IPE: can't select link to send the loss, all broken???"); - } + LOGC(inlog.Error, log << CONID() << "IPE: can't select link to send the loss, all broken???"); + } + } + else + { + ScopedLock lk (m_RecvAckLock); + // In case of no-group-loss-handling, add them now to the + // socket's loss lists. + for (losses_t::const_iterator seqpair = losses.begin(); seqpair != losses.end(); ++seqpair) + { + num += m_pSndLossList->insert(seqpair->first, seqpair->second); } + + } + + if (num) + { + ScopedLock lk (m_StatsLock); + m_stats.sndr.lost.count(num); } #endif @@ -9342,7 +9428,7 @@ void srt::CUDT::updateAfterSrtHandshake(int hsv) } } -int srt::CUDT::packLostData(CPacket& w_packet, steady_clock::time_point& w_origintime) +int srt::CUDT::packLostData(CPacket& w_packet, steady_clock::time_point& w_origintime, int32_t exp_seq) { // protect m_iSndLastDataAck from updating by ACK processing UniqueLock ackguard(m_RecvAckLock); @@ -9351,6 +9437,16 @@ int srt::CUDT::packLostData(CPacket& w_packet, steady_clock::time_point& w_origi while ((w_packet.m_iSeqNo = m_pSndLossList->popLostSeq()) >= 0) { +#if ENABLE_NEW_BONDING + if (exp_seq != SRT_SEQNO_NONE && w_packet.m_iSeqNo != exp_seq) + { + // This is not the sequence we are looking for. + HLOGC(qslog.Debug, log << "packLostData: expected %" << exp_seq << " extracted %" << w_packet.m_iSeqNo + << " - skipping this."); + continue; + } +#endif + // XXX See the note above the m_iSndLastDataAck declaration in core.h // This is the place where the important sequence numbers for // sender buffer are actually managed by this field here. @@ -9360,7 +9456,7 @@ int srt::CUDT::packLostData(CPacket& w_packet, steady_clock::time_point& w_origi // 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(qrlog.Error, log << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo " + LOGC(qslog.Error, log << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo " << w_packet.m_iSeqNo << ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset << ". Continue"); @@ -9373,7 +9469,7 @@ int srt::CUDT::packLostData(CPacket& w_packet, steady_clock::time_point& w_origi }; w_packet.m_iMsgNo = 0; // Message number is not known, setting all 32 bits to 0. - HLOGC(qrlog.Debug, log << "PEER reported LOSS not from the sending buffer - requesting DROP: " + HLOGC(qslog.Debug, log << "PEER reported LOSS not from the sending buffer - requesting DROP: " << "msg=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " SEQ:" << seqpair[0] << " - " << seqpair[1] << "(" << (-offset) << " packets)"); @@ -9386,7 +9482,7 @@ int srt::CUDT::packLostData(CPacket& w_packet, steady_clock::time_point& w_origi const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getPacketRexmitTime(offset); if (tsLastRexmit >= time_nak) { - HLOGC(qrlog.Debug, log << CONID() << "REXMIT: ignoring seqno " + HLOGC(qslog.Debug, log << CONID() << "REXMIT: ignoring seqno " << w_packet.m_iSeqNo << ", last rexmit " << (is_zero(tsLastRexmit) ? "never" : FormatTime(tsLastRexmit)) << " RTT=" << m_iSRTT << " RTTVar=" << m_iRTTVar << " now=" << FormatTime(time_now)); @@ -9404,7 +9500,7 @@ int srt::CUDT::packLostData(CPacket& w_packet, steady_clock::time_point& w_origi SRT_ASSERT(msglen >= 1); seqpair[1] = CSeqNo::incseq(seqpair[0], msglen - 1); - HLOGC(qrlog.Debug, + HLOGC(qslog.Debug, log << "loss-reported packets expired in SndBuf - requesting DROP: " << "msgno=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " msglen=" << msglen << " SEQ:" << seqpair[0] << " - " << seqpair[1]); @@ -9598,9 +9694,14 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime return false; #if ENABLE_NEW_BONDING + + // This part is being used in case of groups that use the scheduler + // for sending packets. The scheduler is used in general for groups that + // pick up selectively the packets that will be sent. Not use for groups + // that simply pick up packets as they come in the scheduling order and priorities. if (m_parent->m_GroupMemberData && m_parent->m_GroupMemberData->use_send_schedule) { - // If this socket is a group member of a group that uses send scheduler, + // If this socket is a group member of a group that uses the send scheduler, // do not extract packets from the existing resources, but instead pick up // the sequence from the scheduler container and extract that sequence from // the sender buffer. @@ -9610,7 +9711,10 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // buffer (XXX not implemented yet) and delivered in order. vector seqs; if (!m_parent->m_GroupOf->getSendSchedule(m_parent->m_GroupMemberData, (seqs))) + { + HLOGP(qslog.Debug, "packData(sched): No scheduled packets yet, nothing to send"); return false; + } int nremoved = 0; for (size_t i = 0; i < seqs.size(); ++i) @@ -9622,41 +9726,110 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime nremoved = i+1; - int msglen; + // XXX see below + //int msglen; + if (ss.type == groups::SQT_PFILTER) { // XXX packet filter extraction currently not implemented, do not use. continue; //filter_ctl_pkt = true; } - else + else if (ss.type == groups::SQT_LOSS) { - const int offset = CSeqNo::seqoff(m_iSndLastDataAck, w_packet.m_iSeqNo); - if (offset < 0) + // This is scheduled retransmission request. The loss list may + // contain more packets specs than this socket needs to retransmit, + // as all sender loss lists should be synchronized in the group. + + // payload = isRetransmissionAllowed(enter_time) ? + // XXX This condition is currently blocked because this was scheduled. + // In this case, as it was picked up and is going to be removed from + // the schedule, we can either execute it right now, or somehow put back + // to the schedule. + payload = m_parent->m_GroupOf->packLostData(this, (w_packet), (origintime), ss.seq); + if (payload == 0) { - // Already removed from the buffer, cannot send. - HLOGC(qslog.Debug, log << "sched seq %" << w_packet.m_iSeqNo << " past the buffer"); + // If this happens, it means that there was a sequence found among + // the loss list, was added to the schedule, and it disappeared, + // without having the schedule updated, or somehow it never was in + // the loss list. Ignore if found, but report an IPE. + LOGC(qslog.Error, log << "packData(sched): IPE: %" << ss.seq + << ":loss in the schedule is missing from the loss list"); continue; } + } + else if (ss.type == groups::SQT_FRESH) + { + // This procedure should pack the new packet, so it should be + // picked up from the top, so this should use the procedure that + // normally picks up the new packet. The difference is that + // 1. You need to pick up the seq that is extracted from the schedule. + // 2. If this sequence is not in the "fresh range", simply skip this + // (and report IPE because this should never happen). + // 3. If this sequence is in the "fresh range" than it can be + // exactly the packet with the required sequence number, or + // otherwise this packet should be simply discarded and the + // operation should be retried. We know that it won't roll in + // infinity because the range has been checked first, so worst + // case scenario you'll have several packets to discard until + // you reach the end of buffer (which should always retrieve + // at least one last packet from the buffer provided that the + // sequence range was first verified. + + SRT_ASSERT( !(m_iSndCurrSeqNo == SRT_SEQNO_NONE || m_iSndNextSeqNo == SRT_SEQNO_NONE) ); + if (m_iSndCurrSeqNo == SRT_SEQNO_NONE || m_iSndNextSeqNo == SRT_SEQNO_NONE) + continue; - // XXX If the sender buffer is made common for the whole group, this should - // simply extract a packet from the common sender buffer. - - payload = m_pSndBuffer->readData(offset, (w_packet), (origintime), (msglen)); - - uint32_t msgno_field = w_packet.m_iMsgNo; + // Ok, FRESH packets must be contained between the last + // sent packet and the newest expected scheduled packet + if (CSeqNo::seqcmp(ss.seq, m_iSndCurrSeqNo) <= 0 // is already sent + || CSeqNo::seqcmp(ss.seq, m_iSndNextSeqNo) >= 0) // wasn't ever scheduled + { + LOGC(qslog.Error, log << "packData(sched): IPE: scheduled %" << ss.seq + << " is outside the fresh range %(" << CSeqNo::incseq(m_iSndCurrSeqNo) + << " - " << CSeqNo::decseq(m_iSndNextSeqNo) << ") - skipping"); + continue; + } - if (ss.type == groups::SQT_LOSS) + // After this condition we know that the situation is: + // The given sequence is one of the o below, + // and the very first call to packUniqueData() will pick + // up packets starting from the next towards m_iSndCurrSeqNo. + // As we know this is one of them, simply pick up packets until + // you get the right one. + // + // | x | x | x | o | o | o | o | . + // + // ^ ^ + // m_iSndCurrSeqNo m_iSndNextSeqNo + + if (!packUniqueData((w_packet), (origintime))) { - msgno_field |= MSGNO_REXMIT::mask; + // Kinda impossible, but still handle it. + payload = 0; + break; } - else + + if (w_packet.m_iSeqNo != ss.seq) { - msgno_field &= ~MSGNO_REXMIT::mask; + // This is not the packet we are looking for. + continue; } - w_packet.m_iMsgNo = msgno_field; + new_packet_packed = true; + + // every 16 (0xF) packets, a packet pair is sent + if ((w_packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0) + probe = true; + + payload = (int) w_packet.getLength(); + reason = "normal"; } + else // SQT_SKIP + { + continue; + } + if (payload == -1) { // To be dropped, ignore. @@ -9687,10 +9860,25 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime else #endif { - - payload = isRetransmissionAllowed(enter_time) - ? packLostData((w_packet), (origintime)) - : 0; + if (isRetransmissionAllowed(enter_time)) + { +#if BROADCAST_COMMON_SND_LOSS + if (m_parent->m_GroupOf && m_parent->m_GroupOf->type() == SRT_GTYPE_BROADCAST) + { + // XXX Note: this is a simplified solution just to test it with broadcast groups. + // If the common losses for broadcast are to be implemented seriously, + // then the losses should be distributed to all member sockets and then + // retransmission should happen just like for single sockets. + payload = m_parent->m_GroupOf->packLostData(this, (w_packet), (origintime), SRT_SEQNO_NONE /* get any seq */); + } + else +#endif + payload = packLostData((w_packet), (origintime)); + } + else + { + payload = 0; + } if (payload > 0) { @@ -11084,53 +11272,8 @@ void srt::CUDT::unlose(const CPacket &packet) if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0) return; - size_t i = 0; - int had_ttl = 0; - for (i = 0; i < m_FreshLoss.size(); ++i) - { - had_ttl = m_FreshLoss[i].ttl; - switch (m_FreshLoss[i].revoke(sequence)) - { - case CRcvFreshLoss::NONE: - continue; // Not found. Search again. - - case CRcvFreshLoss::STRIPPED: - goto breakbreak; // Found and the modification is applied. We're done here. - - case CRcvFreshLoss::DELETE: - // No more elements. Kill it. - m_FreshLoss.erase(m_FreshLoss.begin() + i); - // Every loss is unique. We're done here. - goto breakbreak; - - case CRcvFreshLoss::SPLIT: - // Oh, this will be more complicated. This means that it was in between. - { - // So create a new element that will hold the upper part of the range, - // and this one modify to be the lower part of the range. - - // Keep the current end-of-sequence value for the second element - int32_t next_end = m_FreshLoss[i].seq[1]; - - // seq-1 set to the end of this element - m_FreshLoss[i].seq[1] = CSeqNo::decseq(sequence); - // seq+1 set to the begin of the next element - int32_t next_begin = CSeqNo::incseq(sequence); - - // Use position of the NEXT element because insertion happens BEFORE pointed element. - // Use the same TTL (will stay the same in the other one). - m_FreshLoss.insert(m_FreshLoss.begin() + i + 1, - CRcvFreshLoss(next_begin, next_end, m_FreshLoss[i].ttl)); - } - goto breakbreak; - } - } - - // Could have made the "return" instruction instead of goto, but maybe there will be something - // to add in future, so keeping that. -breakbreak:; - - if (i != m_FreshLoss.size()) + int had_ttl = 0; + if (CRcvFreshLoss::removeOne((m_FreshLoss), sequence, (&had_ttl))) { HLOGF(qrlog.Debug, "sequence %d removed from belated lossreport record", sequence); } diff --git a/srtcore/core.h b/srtcore/core.h index 4c66e5dd7..6cf119b80 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1050,7 +1050,7 @@ class CUDT /// @param origintime [in, out] origin timestamp of the packet /// /// @return payload size on success, <=0 on failure - int packLostData(CPacket &packet, time_point &origintime); + int packLostData(CPacket &packet, time_point &origintime, int32_t exp_seq = SRT_SEQNO_NONE); /// Pack a unique data packet (never sent so far) in CPacket for sending. /// @@ -1069,6 +1069,8 @@ class CUDT /// @retval false Nothing was extracted for sending, @a nexttime should be ignored bool packData(CPacket& packet, time_point& nexttime); + void removeSndLossUpTo(int32_t seq); + int processData(CUnit* unit); int checkLazySpawnLatencyThread(); diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 51200c1a7..fc544b3ae 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -312,7 +312,7 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) } #if ENABLE_NEW_RCVBUFFER -void CUDTGroup::createBuffers(int32_t isn, const time_point& tsbpd_start_time) +void CUDTGroup::createBuffers(int32_t isn, const time_point& tsbpd_start_time, int flow_winsize) { // XXX NOT YET, but will be in use. m_pSndBuffer.reset(); @@ -323,6 +323,8 @@ void CUDTGroup::createBuffers(int32_t isn, const time_point& tsbpd_start_time) HLOGC(gmlog.Debug, log << "grp/createBuffers: setting rcv buf start time=" << FormatTime(tsbpd_start_time) << " lat=" << latency_us() << "us"); m_pRcvBuffer->setTsbPdMode(tsbpd_start_time, false, microseconds_from(latency_us())); } + + m_pSndLossList.reset(new CSndLossList(flow_winsize * 2)); } #endif @@ -960,6 +962,7 @@ void CUDTGroup::syncWithFirstSocket(const CUDT& core, const HandshakeSide side) */ m_RcvLastSeqNo = CSeqNo::decseq(core.ISN()); + m_SndLastDataAck = core.ISN(); if (core.m_bGroupTsbPd) { @@ -969,7 +972,7 @@ void CUDTGroup::syncWithFirstSocket(const CUDT& core, const HandshakeSide side) HLOGC(gmlog.Debug, log << "grp/syncWithFirstSocket: creating receiver buffer for ISN=%" << core.ISN() << " TSBPD start: " << (core.m_bGroupTsbPd ? FormatTime(m_tsRcvPeerStartTime) : "not enabled")); - createBuffers(core.ISN(), m_tsRcvPeerStartTime); + createBuffers(core.ISN(), m_tsRcvPeerStartTime, core.m_iFlowWindowSize); #else // XXX @@ -6092,40 +6095,176 @@ bool CUDTGroup::updateSendPacketUnique(int32_t single_seq) } // Update on received loss report or request to retransmit on NAKREPORT. -bool CUDTGroup::updateSendPacketLoss(const std::vector< std::pair >& seqlist) +bool CUDTGroup::updateSendPacketLoss(bool use_send_sched, const std::vector< std::pair >& seqlist) { - ScopedLock guard(m_GroupLock); + ScopedLock guard(m_LossAckLock); typedef std::vector< std::pair > seqlist_t; - BalancingLinkState lstate = { m_Group.active(), 0, 0 }; + int num = 0; // for stats + // Add the loss list to the groups loss list for (seqlist_t::const_iterator seqpair = seqlist.begin(); seqpair != seqlist.end(); ++seqpair) { - // These are loss ranges, so believe that they are in order. - pair begin_end = *seqpair; - // The seqpair in the loss list is the first and last, both including, - // except when there's only one, in which case it's twice the same value. - // Increase the end seq by one to make it the "past the end seq". - begin_end.second = CSeqNo::incseq(begin_end.second); + num += m_pSndLossList->insert(seqpair->first, seqpair->second); + } - for (int32_t seq = begin_end.first; seq != begin_end.second; seq = CSeqNo::incseq(seq)) + if (use_send_sched) + { + BalancingLinkState lstate = { m_Group.active(), 0, 0 }; + + for (seqlist_t::const_iterator seqpair = seqlist.begin(); seqpair != seqlist.end(); ++seqpair) { - // Select a link to use for every sequence. - gli_t selink = CALLBACK_CALL(m_cbSelectLink, lstate); - if (selink == m_Group.end()) + // These are loss ranges, so believe that they are in order. + pair begin_end = *seqpair; + // The seqpair in the loss list is the first and last, both including, + // except when there's only one, in which case it's twice the same value. + // Increase the end seq by one to make it the "past the end seq". + begin_end.second = CSeqNo::incseq(begin_end.second); + + for (int32_t seq = begin_end.first; seq != begin_end.second; seq = CSeqNo::incseq(seq)) { - // Interrupt all - we have no link candidates to send. - return false; + // Select a link to use for every sequence. + gli_t selink = CALLBACK_CALL(m_cbSelectLink, lstate); + if (selink == m_Group.end()) + { + // Interrupt all - we have no link candidates to send. + return false; + } + + selink->send_schedule.push_back((SchedSeq){seq, groups::SQT_LOSS}); + lstate.ilink = selink; } + } + + m_Group.set_active(lstate.ilink); + } + return true; +} + +void CUDTGroup::updateSndLossListOnACK(int32_t ackdata_seqno) +{ + ScopedLock guard(m_LossAckLock); + if (CSeqNo::seqcmp(m_SndLastDataAck, ackdata_seqno) < 0) + { + // remove any loss that predates 'ack' (not to be considered loss anymore) + m_pSndLossList->removeUpTo(CSeqNo::decseq(ackdata_seqno)); + m_SndLastDataAck = ackdata_seqno; + } +} + +// This is almost a copy of the CUDT::packLostData except that: +// - it uses a separate mechanism to extract the selected sequence number +// (which is known from the schedule, while the schedule is filled upon incoming loss request) +// - it doesn't check if the loss was received too early (it's more complicated this time) +int CUDTGroup::packLostData(CUDT* core, CPacket& w_packet, steady_clock::time_point& w_origintime, int32_t exp_seq) +{ + // protect m_iSndLastDataAck from updating by ACK processing + UniqueLock ackguard(m_LossAckLock); + //const steady_clock::time_point time_now = steady_clock::now(); + //const steady_clock::time_point time_nak = time_now - microseconds_from(core->m_iSRTT - 4 * core->m_iRTTVar); + + // XXX This is temporarily used for broadcast with common loss list. + bool have_extracted = false; + const char* as = "FIRST FOUND"; + if (exp_seq == SRT_SEQNO_NONE) + { + exp_seq = m_pSndLossList->popLostSeq(); + have_extracted = (exp_seq != SRT_SEQNO_NONE); + } + else + { + as = "EXPECTED"; + have_extracted = m_pSndLossList->popLostSeq(exp_seq); + } + + HLOGC(qslog.Debug, log << "CUDTGroup::packLostData: " << (have_extracted ? "" : "NOT") << " extracted " + << as << " %" << exp_seq); + + if (have_extracted) + { + w_packet.m_iSeqNo = exp_seq; + + // XXX See the note above the m_iSndLastDataAck declaration in core.h + // This is the place where the important sequence numbers for + // sender buffer are actually managed by this field here. + const int offset = CSeqNo::seqoff(core->m_iSndLastDataAck, w_packet.m_iSeqNo); + if (offset < 0) + { + // 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 << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo " + << w_packet.m_iSeqNo << ", m_iSndLastDataAck " << core->m_iSndLastDataAck + << ")=" << offset << ". Continue"); + + // 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] = { + w_packet.m_iSeqNo, + CSeqNo::decseq(core->m_iSndLastDataAck) + }; + w_packet.m_iMsgNo = 0; // Message number is not known, setting all 32 bits to 0. - selink->send_schedule.push_back((SchedSeq){seq, groups::SQT_LOSS}); - lstate.ilink = selink; + HLOGC(qslog.Debug, log << "PEER reported LOSS not from the sending buffer - requesting DROP: " + << "msg=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " SEQ:" + << seqpair[0] << " - " << seqpair[1] << "(" << (-offset) << " packets)"); + + core->sendCtrl(UMSG_DROPREQ, &w_packet.m_iMsgNo, seqpair, sizeof(seqpair)); + return 0; } + + int msglen; + const int payload = core->m_pSndBuffer->readData(offset, (w_packet), (w_origintime), (msglen)); + if (payload == -1) + { + int32_t seqpair[2]; + seqpair[0] = w_packet.m_iSeqNo; + SRT_ASSERT(msglen >= 1); + seqpair[1] = CSeqNo::incseq(seqpair[0], msglen - 1); + + HLOGC(qslog.Debug, + log << "loss-reported packets expired in SndBuf - requesting DROP: " + << "msgno=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " msglen=" << msglen + << " SEQ:" << seqpair[0] << " - " << seqpair[1]); + core->sendCtrl(UMSG_DROPREQ, &w_packet.m_iMsgNo, seqpair, sizeof(seqpair)); + + // skip all dropped packets + m_pSndLossList->removeUpTo(seqpair[1]); + core->m_iSndCurrSeqNo = CSeqNo::maxseq(core->m_iSndCurrSeqNo, seqpair[1]); + return 0; + } + else if (payload == 0) + return 0; + + // At this point we no longer need the ACK lock, + // because we are going to return from the function. + // Therefore unlocking in order not to block other threads. + ackguard.unlock(); + + enterCS(core->m_StatsLock); + core->m_stats.sndr.sentRetrans.count(payload); + leaveCS(core->m_StatsLock); + + // Despite the contextual interpretation of packet.m_iMsgNo around + // CSndBuffer::readData version 2 (version 1 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 (core->m_bPeerRexmitFlag) + { + w_packet.m_iMsgNo |= PACKET_SND_REXMIT; + } + + return payload; + } + else + { + // This is not the sequence we are looking for. + HLOGC(qslog.Debug, log << "packLostData: expected %" << exp_seq << " not found in the group's loss list"); } - m_Group.set_active(lstate.ilink); - return true; + return 0; } SRT_ATR_NODISCARD bool CUDTGroup::getSendSchedule(SocketData* d, vector& w_seqs) diff --git a/srtcore/group.h b/srtcore/group.h index a53c17b65..97da7825e 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -115,7 +115,7 @@ class CUDTGroup ~CUDTGroup(); #if ENABLE_NEW_RCVBUFFER - void createBuffers(int32_t isn, const time_point& tsbpd_start_time); + void createBuffers(int32_t isn, const time_point& tsbpd_start_time, int flow_winsize); #endif SocketData* add(SocketData data); @@ -641,8 +641,13 @@ class CUDTGroup #if ENABLE_NEW_RCVBUFFER UniquePtr m_pSndBuffer; // XXX for future. + + SRT_ATTR_PT_GUARDED_BY(m_RcvBufferLock) UniquePtr m_pRcvBuffer; + SRT_ATTR_PT_GUARDED_BY(m_LossAckLock) + UniquePtr m_pSndLossList; + sync::CThread m_RcvTsbPdThread; // Rcv TsbPD Thread handle static void* tsbpd(void* param); @@ -725,6 +730,7 @@ class CUDTGroup #if ENABLE_NEW_RCVBUFFER sync::atomic m_RcvLastSeqNo; + sync::atomic m_SndLastDataAck; #else void recv_CollectAliveAndBroken(std::vector& w_alive, std::set& w_broken); @@ -774,6 +780,7 @@ class CUDTGroup sync::atomic m_bTsbpdWaitForNewPacket; // TSBPD forever-wait should be signaled by new packet reception sync::atomic m_bTsbpdWaitForExtraction;// TSBPD forever-wait should be signaled by extracting the last ready packet mutable sync::Mutex m_RcvBufferLock; // Protects the state of the m_pRcvBuffer + mutable sync::Mutex m_LossAckLock; #endif sync::atomic m_iLastSchedSeqNo; // represetnts the value of CUDT::m_iSndNextSeqNo for each running socket @@ -957,10 +964,12 @@ class CUDTGroup #if ENABLE_NEW_RCVBUFFER SRT_ATR_NODISCARD bool updateSendPacketUnique(int32_t single_seq); - SRT_ATR_NODISCARD bool updateSendPacketLoss(const std::vector< std::pair >& seqlist); + SRT_ATR_NODISCARD bool updateSendPacketLoss(bool use_send_sched, const std::vector< std::pair >& seqlist); SRT_ATR_NODISCARD bool getSendSchedule(SocketData* d, std::vector& w_sched); void discardSendSchedule(SocketData* d, int ndiscard); + void updateSndLossListOnACK(int32_t ackdata_seqno); + int packLostData(CUDT* core, CPacket& w_packet, steady_clock::time_point& w_origintime, int32_t exp_seq); time_point getPktTsbPdTime(uint32_t usPktTimestamp) const { diff --git a/srtcore/group_common.h b/srtcore/group_common.h index ab3048f5e..eaddfe296 100644 --- a/srtcore/group_common.h +++ b/srtcore/group_common.h @@ -32,9 +32,19 @@ namespace groups enum SeqType { + /// Freshly first-time to be sent packets. SQT_FRESH, + + /// Rexmit requests SQT_LOSS, - SQT_PFILTER + + /// Packet filter requests + SQT_PFILTER, + + /// Special value used in case when the request + /// has been exceptionally cancelled, but removal + /// of the element would violate the logics. + SQT_SKIP }; struct SchedSeq diff --git a/srtcore/list.cpp b/srtcore/list.cpp index 01b034781..657653211 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -109,21 +109,21 @@ void srt::CSndLossList::traceState() const std::cout << "\n"; } -int srt::CSndLossList::insert(int32_t seqno1, int32_t seqno2) +int srt::CSndLossList::insert(int32_t seqlo, int32_t seqhi) { - if (seqno1 < 0 || seqno2 < 0 ) { - LOGC(qslog.Error, log << "IPE: Tried to insert negative seqno " << seqno1 << ":" << seqno2 + if (seqlo < 0 || seqhi < 0 ) { + LOGC(qslog.Error, log << "IPE: Tried to insert negative seqno " << seqlo << ":" << seqhi << " into sender's loss list. Ignoring."); return 0; } - // Make sure that seqno2 isn't earlier than seqno1. - SRT_ASSERT(CSeqNo::seqcmp(seqno1, seqno2) <= 0); + // Make sure that seqhi isn't earlier than seqlo. + SRT_ASSERT(CSeqNo::seqcmp(seqlo, seqhi) <= 0); - const int inserted_range = CSeqNo::seqlen(seqno1, seqno2); + const int inserted_range = CSeqNo::seqlen(seqlo, seqhi); if (inserted_range <= 0 || inserted_range >= m_iSize) { LOGC(qslog.Error, log << "IPE: Tried to insert too big range of seqno: " << inserted_range << ". Ignoring. " - << "seqno " << seqno1 << ":" << seqno2); + << "seqno " << seqlo << ":" << seqhi); return 0; } @@ -131,19 +131,19 @@ int srt::CSndLossList::insert(int32_t seqno1, int32_t seqno2) if (m_iLength == 0) { - insertHead(0, seqno1, seqno2); + insertHead(0, seqlo, seqhi); return m_iLength; } // Find the insert position in the non-empty list const int origlen = m_iLength; - const int offset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seqno1); + const int offset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seqlo); if (offset >= m_iSize) { LOGC(qslog.Error, log << "IPE: New loss record is too far from the first record. Ignoring. " << "First loss seqno " << m_caSeq[m_iHead].seqstart - << ", insert seqno " << seqno1 << ":" << seqno2); + << ", insert seqno " << seqlo << ":" << seqhi); return 0; } @@ -151,7 +151,7 @@ int srt::CSndLossList::insert(int32_t seqno1, int32_t seqno2) if (loc < 0) { - const int offset_seqno2 = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seqno2); + const int offset_seqno2 = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seqhi); const int loc_seqno2 = (m_iHead + offset_seqno2 + m_iSize) % m_iSize; if (loc_seqno2 < 0) @@ -161,7 +161,7 @@ int srt::CSndLossList::insert(int32_t seqno1, int32_t seqno2) // If the new loss does not fit, there is some error. LOGC(qslog.Error, log << "IPE: New loss record is too old. Ignoring. " << "First loss seqno " << m_caSeq[m_iHead].seqstart - << ", insert seqno " << seqno1 << ":" << seqno2); + << ", insert seqno " << seqlo << ":" << seqhi); return 0; } @@ -170,49 +170,49 @@ int srt::CSndLossList::insert(int32_t seqno1, int32_t seqno2) if (offset < 0) { - insertHead(loc, seqno1, seqno2); + insertHead(loc, seqlo, seqhi); } else if (offset > 0) { - if (seqno1 == m_caSeq[loc].seqstart) + if (seqlo == m_caSeq[loc].seqstart) { - const bool updated = updateElement(loc, seqno1, seqno2); + const bool updated = updateElement(loc, seqlo, seqhi); if (!updated) return 0; } else { // Find the prior node. - // It should be the highest sequence number less than seqno1. + // It should be the highest sequence number less than seqlo. // 1. Start the search either from m_iHead, or from m_iLastInsertPos int i = m_iHead; - if ((m_iLastInsertPos != -1) && (CSeqNo::seqcmp(m_caSeq[m_iLastInsertPos].seqstart, seqno1) < 0)) + if ((m_iLastInsertPos != -1) && (CSeqNo::seqcmp(m_caSeq[m_iLastInsertPos].seqstart, seqlo) < 0)) i = m_iLastInsertPos; - // 2. Find the highest sequence number less than seqno1. - while (m_caSeq[i].inext != -1 && CSeqNo::seqcmp(m_caSeq[m_caSeq[i].inext].seqstart, seqno1) < 0) + // 2. Find the highest sequence number less than seqlo. + while (m_caSeq[i].inext != -1 && CSeqNo::seqcmp(m_caSeq[m_caSeq[i].inext].seqstart, seqlo) < 0) i = m_caSeq[i].inext; - // 3. Check if seqno1 overlaps with (seqbegin, seqend) + // 3. Check if seqlo overlaps with (seqbegin, seqend) const int seqend = m_caSeq[i].seqend == SRT_SEQNO_NONE ? m_caSeq[i].seqstart : m_caSeq[i].seqend; - if (CSeqNo::seqcmp(seqend, seqno1) < 0 && CSeqNo::incseq(seqend) != seqno1) + if (CSeqNo::seqcmp(seqend, seqlo) < 0 && CSeqNo::incseq(seqend) != seqlo) { // No overlap // TODO: Here we should actually insert right after i, not at loc. - insertAfter(loc, i, seqno1, seqno2); + insertAfter(loc, i, seqlo, seqhi); } else { - // TODO: Replace with updateElement(i, seqno1, seqno2). + // TODO: Replace with updateElement(i, seqlo, seqhi). // Some changes to updateElement(..) are required. m_iLastInsertPos = i; - if (CSeqNo::seqcmp(seqend, seqno2) >= 0) + if (CSeqNo::seqcmp(seqend, seqhi) >= 0) return 0; // overlap, coalesce with prior node, insert(3, 7) to [2, 5], ... becomes [2, 7] - m_iLength += CSeqNo::seqlen(seqend, seqno2) - 1; - m_caSeq[i].seqend = seqno2; + m_iLength += CSeqNo::seqlen(seqend, seqhi) - 1; + m_caSeq[i].seqend = seqhi; loc = i; } @@ -220,7 +220,7 @@ int srt::CSndLossList::insert(int32_t seqno1, int32_t seqno2) } else // offset == 0, loc == m_iHead { - const bool updated = updateElement(m_iHead, seqno1, seqno2); + const bool updated = updateElement(m_iHead, seqlo, seqhi); if (!updated) return 0; } @@ -358,6 +358,14 @@ int32_t srt::CSndLossList::popLostSeq() return SRT_SEQNO_NONE; } + return popLostSeq_internal(); +} + +/// Extract the earliest sequence number from the container and return it. +/// If found, it is removed from the container. +/// If the container is empty, return SRT_SEQNO_NONE. +int32_t srt::CSndLossList::popLostSeq_internal() +{ if (m_iLastInsertPos == m_iHead) m_iLastInsertPos = -1; @@ -377,6 +385,8 @@ int32_t srt::CSndLossList::popLostSeq() int loc = (m_iHead + 1) % m_iSize; m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); + + // XXX likely this condition can simply check if old end != seqstart if (CSeqNo::seqcmp(m_caSeq[m_iHead].seqend, m_caSeq[loc].seqstart) > 0) m_caSeq[loc].seqend = m_caSeq[m_iHead].seqend; @@ -392,6 +402,143 @@ int32_t srt::CSndLossList::popLostSeq() return seqno; } +/// This function returns a similar value to CSeqNo::seqcmp(), +/// except that it checks against the range from @a seqlo to @a seqhi. +/// It returns 0 in case when @a seq is in this range. Otherwise +/// if it precedes this range, the returned value is the comparison +/// result against @a seqlo, and if it succeeds the range, the +/// comparison result with @a seqhi, or, if it is SRT_SEQNO_NONE, +/// with @a seqlo. +/// This function is using specific rules of CSndLossList. +int srt::CSndLossList::rangecmp(int32_t seq, int32_t seqlo, int32_t seqhi) +{ + SRT_ASSERT(seqlo != SRT_SEQNO_NONE); + + int cmp = CSeqNo::seqcmp(seq, seqlo); + if (seqhi == SRT_SEQNO_NONE || cmp < 0) + return cmp; + + // Since now seq may be only >= seqlo, check seqhi + cmp = CSeqNo::seqcmp(seq, seqhi); + if (cmp > 0) + return cmp; + + return 0; +} + +/// Find given sequence in the container. If found, remove it from +/// the container and return true. +/// If not found, it returns false, and the container is unchanged. +bool srt::CSndLossList::popLostSeq(int32_t seq) +{ + ScopedLock listguard(m_ListLock); + + if (m_iLength == 0) + return false; // nothing in the container anyway + + if (seq == m_caSeq[m_iHead].seqstart) + { + // Pop the very first sequence. + int seqr = popLostSeq_internal(); + + // All internal state has been modified accordingly. + return (seqr == seq); + } + + int loc = m_iHead; + int* prev_next = &m_iHead; + for (;;) + { + Seq& cell = m_caSeq[loc]; + int cmp = rangecmp(seq, cell.seqstart, cell.seqend); + if (cmp < 0) + { + // Ranges collected here are increasing, so if this isn't present + // in this range and precedes it, and all "previous" ranges have been + // checked already, this means that this sequence isn't in the list. + + // Do nothing and return false - nothing has been modified. + return false; + } + + if (cmp > 0) + { + // Otherwise, this is possibly in any of the following loss ranges, so + // unless we reached the end of list, + if (cell.inext == -1) + break; + + // continue with the next one. + prev_next = &cell.inext; + loc = cell.inext; + continue; + } + + // You hit it right on the head. Now check optimistic edge. + + if (cell.seqend == SRT_SEQNO_NONE) + { + // Nice. One single entry. Take the next one + // and rebind to the preceding element link. + cell.seqstart = SRT_SEQNO_NONE; + *prev_next = cell.inext; + } + else if (seq == cell.seqend) + { + // Simple - just slash one value from the end, + // all elements remain at their positions. + cell.seqend = CSeqNo::decseq(seq); + if (cell.seqend == cell.seqstart) + cell.seqend = SRT_SEQNO_NONE; + } + else if (seq != cell.seqstart) + { + // We are in the middle, so the current + // element stays, just gets slashed, and + // the new element has to be created. + int offset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seq); + int newloc = (m_iHead + offset + m_iSize) % m_iSize; + m_caSeq[newloc].seqstart = CSeqNo::incseq(seq); + if (m_caSeq[newloc].seqstart != cell.seqend) + { + // If they were equal, seqend of the new cell should + // remain cleared. If they are not, copy from the current cell. + m_caSeq[newloc].seqend = cell.seqend; + } + + // Ok, now update the upper range + cell.seqend = CSeqNo::decseq(seq); + if (cell.seqend == cell.seqstart) + cell.seqend = SRT_SEQNO_NONE; + } + else + { + // This is the beginning sequence of the range containing + // more than 1 element. This means that we need to MOVE + // this element and update the previous element. + + int offset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seq); + int newloc = (m_iHead + offset + m_iSize) % m_iSize; + m_caSeq[newloc].seqstart = CSeqNo::incseq(seq); + if (m_caSeq[newloc].seqstart != cell.seqend) + { + // If they were equal, seqend of the new cell should + // remain cleared. If they are not, copy from the current cell. + m_caSeq[newloc].seqend = cell.seqend; + } + + *prev_next = newloc; + cell.seqstart = SRT_SEQNO_NONE; + cell.seqend = SRT_SEQNO_NONE; + } + + m_iLength --; + return true; + } + + return false; +} + void srt::CSndLossList::insertHead(int pos, int32_t seqno1, int32_t seqno2) { SRT_ASSERT(pos >= 0); @@ -874,3 +1021,64 @@ srt::CRcvFreshLoss::Emod srt::CRcvFreshLoss::revoke(int32_t lo, int32_t hi) return DELETE; } + +bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32_t sequence, int* pw_had_ttl) +{ + size_t i = 0; + int had_ttl = 0; + for (i = 0; i < w_container.size(); ++i) + { + had_ttl = w_container[i].ttl; + switch (w_container[i].revoke(sequence)) + { + case CRcvFreshLoss::NONE: + continue; // Not found. Search again. + + case CRcvFreshLoss::STRIPPED: + goto breakbreak; // Found and the modification is applied. We're done here. + + case CRcvFreshLoss::DELETE: + // No more elements. Kill it. + w_container.erase(w_container.begin() + i); + // Every loss is unique. We're done here. + goto breakbreak; + + case CRcvFreshLoss::SPLIT: + // Oh, this will be more complicated. This means that it was in between. + { + // So create a new element that will hold the upper part of the range, + // and this one modify to be the lower part of the range. + + // Keep the current end-of-sequence value for the second element + int32_t next_end = w_container[i].seq[1]; + + // seq-1 set to the end of this element + w_container[i].seq[1] = CSeqNo::decseq(sequence); + // seq+1 set to the begin of the next element + int32_t next_begin = CSeqNo::incseq(sequence); + + // Use position of the NEXT element because insertion happens BEFORE pointed element. + // Use the same TTL (will stay the same in the other one). + w_container.insert(w_container.begin() + i + 1, + CRcvFreshLoss(next_begin, next_end, w_container[i].ttl)); + } + goto breakbreak; + } + } + + // Could have made the "return" instruction instead of goto, but maybe there will be something + // to add in future, so keeping that. +breakbreak: + ; + + if (pw_had_ttl) + *pw_had_ttl = had_ttl; + + if (i != w_container.size()) + { + return true; + } + + return false; +} + diff --git a/srtcore/list.h b/srtcore/list.h index 03f05e927..ed67afc1f 100644 --- a/srtcore/list.h +++ b/srtcore/list.h @@ -53,6 +53,8 @@ modified by #ifndef INC_SRT_LIST_H #define INC_SRT_LIST_H +#include + #include "udt.h" #include "common.h" @@ -81,6 +83,11 @@ class CSndLossList /// Read the first (smallest) loss seq. no. in the list and remove it. /// @return The seq. no. or -1 if the list is empty. int32_t popLostSeq(); + int32_t popLostSeq_internal(); // Part skipping empty and locking + + /// Find the given sequence number in the container and remove it, if found. + /// @return true if the sequence was found and removed, false otherwise. + bool popLostSeq(int32_t seqno); void traceState() const; @@ -118,6 +125,10 @@ class CSndLossList /// @param seqno2 last sequence number in range (SRT_SEQNO_NONE if no range) bool updateElement(int pos, int32_t seqno1, int32_t seqno2); + static int rangecmp(int32_t seq, int32_t seqlo, int32_t seqhi); + + static const int LOC_NONE = -1; + private: CSndLossList(const CSndLossList&); CSndLossList& operator=(const CSndLossList&); @@ -247,7 +258,7 @@ struct CRcvFreshLoss int ttl; srt::sync::steady_clock::time_point timestamp; - CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_ttl); + CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_ttl = 1); // Don't WTF when looking at this. The Windows system headers define // a publicly visible preprocessor macro with that name. REALLY! @@ -264,8 +275,11 @@ struct CRcvFreshLoss Emod revoke(int32_t sequence); Emod revoke(int32_t lo, int32_t hi); + + static bool removeOne(std::deque& w_container, int32_t sequence, int* had_ttl = NULL); }; + } // namespace srt #endif diff --git a/srtcore/utilities.h b/srtcore/utilities.h index 0a66175e5..203b1bf8f 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -534,6 +534,34 @@ namespace srt_pair_op } } +namespace any_op +{ + template + struct AnyProxy + { + const T& value; + bool result; + + AnyProxy(const T& x, bool res): value(x), result(res) {} + + AnyProxy& operator,(const T& val) + { + if (result) + return *this; + result = value == val; + return *this; + } + + operator bool() { return result; } + }; + + template inline + AnyProxy EqualAny(const T& checked_val) + { + return AnyProxy(checked_val, false); + } +} + #if HAVE_CXX11 template From 92639ad2210cecf3717723c5750b38d3c1df5180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 22 Sep 2022 09:58:02 +0200 Subject: [PATCH 13/62] Implemented receiver loss management for new bonding --- srtcore/api.h | 9 + srtcore/buffer_rcv.cpp | 52 +- srtcore/buffer_rcv.h | 13 + srtcore/core.cpp | 1066 +++++++++++++++++++++++++-------------- srtcore/core.h | 13 + srtcore/group.cpp | 141 ++++-- srtcore/group.h | 8 +- srtcore/list.cpp | 1 + srtcore/srt_attr_defs.h | 14 +- 9 files changed, 872 insertions(+), 445 deletions(-) diff --git a/srtcore/api.h b/srtcore/api.h index ca812bf9e..551590e3e 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -193,6 +193,15 @@ class CUDTSocket /// to finish sending the data that were scheduled for sending so far. void setClosed(); + // This is necessary to be called from the group before the group clears + // the connection with the socket. As for managed groups (and there are + // currently no other group types), a socket disconnected from the group + // is no longer usable. + void setClosing() + { + core().m_bClosing = true; + } + /// This does the same as setClosed, plus sets the m_bBroken to true. /// Such a socket can still be read from so that remaining data from /// the receiver buffer can be read, but no longer sends anything. diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index b9cfa1d02..c23f3382b 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -105,6 +105,12 @@ CRcvBufferNew::~CRcvBufferNew() } } +void CRcvBufferNew::debugShowState(const char* source SRT_ATR_UNUSED) +{ + HLOGC(brlog.Debug, log << "RCV-BUF-STATE(" << source << ") start=" << m_iStartPos << " end=" << m_iEndPos + << " drop=" << m_iDropPos << " max-off=+" << m_iMaxPosOff << " seq[start]=%" << m_iStartSeqNo); +} + CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) { SRT_ASSERT(unit != NULL); @@ -124,6 +130,7 @@ CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) IF_RCVBUF_DEBUG(scoped_log.ss << " returns -2"); return InsertInfo(InsertInfo::BELATED); } + IF_HEAVY_LOGGING(string debug_source = "insert %" + Sprint(seqno)); if (offset >= (int)capacity()) { @@ -151,6 +158,8 @@ CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) avail_range = 1; } + IF_HEAVY_LOGGING(debugShowState((debug_source + " overflow").c_str())); + return InsertInfo(InsertInfo::DISCREPANCY, avail_seq, avail_range); } @@ -176,6 +185,7 @@ CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) if (m_entries[newpktpos].status != EntryState_Empty) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -1"); + IF_HEAVY_LOGGING(debugShowState((debug_source + " redundant").c_str())); return InsertInfo(InsertInfo::REDUNDANT); } SRT_ASSERT(m_entries[newpktpos].pUnit == NULL); @@ -312,6 +322,7 @@ CRcvBufferNew::InsertInfo CRcvBufferNew::insert(CUnit* unit) } IF_RCVBUF_DEBUG(scoped_log.ss << " returns 0 (OK)"); + IF_HEAVY_LOGGING(debugShowState((debug_source + " ok").c_str())); if (avail_packet) return InsertInfo(InsertInfo::INSERTED, avail_packet->getSeqNo(), avail_range, earlier_time); @@ -402,14 +413,9 @@ int CRcvBufferNew::dropUpTo(int32_t seqno) // (This call MAY shift m_iStartSeqNo further.) releaseNextFillerEntries(); - // Check only m_iDropPos. The m_iEndPos pointer must - // point either to the same cell as m_iDropPos, or earlier. - if (cmpPos(m_iDropPos, m_iStartPos) < 0) - { - // Start from here and search fort the next gap - m_iEndPos = m_iDropPos = m_iStartSeqNo; - updateGapInfo(incPos(m_iStartPos, m_iMaxPosOff)); - } + // Start from here and search fort the next gap + m_iEndPos = m_iDropPos = m_iStartPos; + updateGapInfo(incPos(m_iStartPos, m_iMaxPosOff)); // Set nonread position to the starting position before updating, // because start position was increased, and preceeding packets are invalid. @@ -417,6 +423,8 @@ int CRcvBufferNew::dropUpTo(int32_t seqno) updateNonreadPos(); if (!m_tsbpd.isEnabled() && m_bMessageAPI) updateFirstReadableRandom(); + + IF_HEAVY_LOGGING(debugShowState(("drop %" + Sprint(seqno)).c_str())); return iDropCnt; } @@ -481,6 +489,7 @@ int CRcvBufferNew::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) m_iFirstRandomMsgPos = -1; updateFirstReadableRandom(); } + IF_HEAVY_LOGGING(debugShowState(("dropmsg off %" + Sprint(seqnolo)).c_str())); return iDropCnt; } @@ -530,9 +539,30 @@ int CRcvBufferNew::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) updateFirstReadableRandom(); } + IF_HEAVY_LOGGING(debugShowState(("dropmsg off %" + Sprint(seqnolo)).c_str())); return iDropCnt; } +bool CRcvBufferNew::getContiguousEnd(int32_t& w_seq) const +{ + if (m_iStartPos == m_iEndPos) + { + // Initial contiguous region empty (including empty buffer). + HLOGC(rbuflog.Debug, log << "CONTIG: empty, give up base=%" << m_iStartSeqNo); + w_seq = m_iStartSeqNo; + return m_iMaxPosOff > 0; + } + + int end_off = offPos(m_iStartPos, m_iEndPos); + + w_seq = CSeqNo::incseq(m_iStartSeqNo, end_off); + + HLOGC(rbuflog.Debug, log << "CONTIG: endD=" << end_off << " maxD=" << m_iMaxPosOff << " base=%" << m_iStartSeqNo + << " end=%" << w_seq); + + return (end_off < m_iMaxPosOff); +} + int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair* pw_seqrange) { const bool canReadInOrder = hasReadableInorderPkts(); @@ -708,6 +738,7 @@ int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pai if (pw_seqrange) *pw_seqrange = make_pair(out_seqlo, out_seqhi); + IF_HEAVY_LOGGING(debugShowState("readmsg")); return bytes_read; } @@ -812,6 +843,7 @@ int CRcvBufferNew::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) LOGC(rbuflog.Error, log << "readBufferTo: 0 bytes read. m_iStartPos=" << m_iStartPos << ", m_iFirstNonreadPos=" << m_iFirstNonreadPos); } + IF_HEAVY_LOGGING(debugShowState("readbuf")); return iBytesRead; } @@ -1380,7 +1412,11 @@ int32_t CRcvBufferNew::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) // Check if it's still inside the buffer if (offset < 0 || offset >= m_iMaxPosOff) + { + HLOGC(rbuflog.Debug, log << "getFirstLossSeq: offset=" << offset << " for %" << fromseq + << " (with max=" << m_iMaxPosOff << ") - NO LOSS FOUND"); return SRT_SEQNO_NONE; + } // Start position int pos = incPos(m_iStartPos, offset); diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 03eb8ab84..4dbbea417 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -222,6 +222,8 @@ class CRcvBufferNew public: + void debugShowState(const char* source); + struct InsertInfo { enum Result { INSERTED = 0, REDUNDANT = -1, BELATED = -2, DISCREPANCY = -3 } result; @@ -276,6 +278,17 @@ class CRcvBufferNew /// @return the number of packets actually dropped. int dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno); + /// Extract the "expected next" packet sequence. + /// Extract the past-the-end sequence for the first packet + /// that is expected to arrive next with preserving the packet order. + /// If the buffer is empty or the very first cell is lacking a packet, + /// it returns the sequence assigned to the first cell. Otherwise it + /// returns the sequence representing the first empty cell (the next + /// cell to the last received packet, if there are no loss-holes). + /// @param [out] w_seq: returns the sequence (always valid) + /// @return true if this sequence is followed by any valid packets + bool getContiguousEnd(int32_t& w_seq) const; + /// Read the whole message from one or several packets. /// /// @param [in,out] data buffer to write the message into. diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 079693614..98c0f4339 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -61,6 +61,7 @@ modified by #include #include #include +#include "srt_attr_defs.h" #include "srt.h" #include "queue.h" #include "api.h" @@ -3266,9 +3267,9 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 return gp->id(); } +//[[using GroupKeeper(gp)]] void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) { - ScopedLock gl (*gp->exp_groupLock()); // We have blocked here the process of connecting a new // socket and adding anything new to the group, so no such @@ -3278,7 +3279,13 @@ void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) start_time = m_stats.tsStartTime; peer_start_time = m_tsRcvPeerStartTime; - if (!gp->applyGroupTime((start_time), (peer_start_time))) + bool first_time = false; + { + ScopedLock gl (*gp->exp_groupLock()); + first_time = gp->applyGroupTime((start_time), (peer_start_time)); + } + + if (!first_time) { HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID << " DERIVED: ST=" @@ -3303,7 +3310,15 @@ void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) steady_clock::time_point rcv_buffer_time_base; bool rcv_buffer_wrap_period = false; steady_clock::duration rcv_buffer_udrift(0); - if (m_bTsbPd && gp->getBufferTimeBase(this, (rcv_buffer_time_base), (rcv_buffer_wrap_period), (rcv_buffer_udrift))) + + first_time = false; + if (m_bTsbPd) + { + ScopedLock gl (*gp->exp_groupLock()); + first_time = gp->getBufferTimeBase(this, (rcv_buffer_time_base), (rcv_buffer_wrap_period), (rcv_buffer_udrift)); + } + + if (m_bTsbPd && first_time) { // We have at least one socket in the group, each socket should have // the value of the timebase set exactly THE SAME. @@ -3348,32 +3363,30 @@ void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) #endif - /* - if (gp->synconmsgno()) + // These are the values that are normally set initially by setters. + int32_t snd_isn = m_iSndLastAck, rcv_isn = m_iRcvLastAck; + + first_time = false; { - HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID << ": NOT synchronizing sequence numbers."); + ScopedLock gl (*gp->exp_groupLock()); + first_time = gp->applyGroupSequences(m_SocketID, (snd_isn), (rcv_isn)); + } + + if (!first_time) + { + HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID + << " DERIVED ISN: RCV=%" << m_iRcvLastAck << " -> %" << rcv_isn + << " (shift by " << CSeqNo::seqcmp(rcv_isn, m_iRcvLastAck) + << ") SND=%" << m_iSndLastAck << " -> %" << snd_isn + << " (shift by " << CSeqNo::seqcmp(snd_isn, m_iSndLastAck) << ")"); + setInitialRcvSeq(rcv_isn); + setInitialSndSeq(snd_isn); } else - */ { - // These are the values that are normally set initially by setters. - int32_t snd_isn = m_iSndLastAck, rcv_isn = m_iRcvLastAck; - if (!gp->applyGroupSequences(m_SocketID, (snd_isn), (rcv_isn))) - { - HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID - << " DERIVED ISN: RCV=%" << m_iRcvLastAck << " -> %" << rcv_isn - << " (shift by " << CSeqNo::seqcmp(rcv_isn, m_iRcvLastAck) - << ") SND=%" << m_iSndLastAck << " -> %" << snd_isn - << " (shift by " << CSeqNo::seqcmp(snd_isn, m_iSndLastAck) << ")"); - setInitialRcvSeq(rcv_isn); - setInitialSndSeq(snd_isn); - } - else - { - HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID - << " DEFINED ISN: RCV=%" << m_iRcvLastAck - << " SND=%" << m_iSndLastAck); - } + HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID + << " DEFINED ISN: RCV=%" << m_iRcvLastAck + << " SND=%" << m_iSndLastAck); } } #endif @@ -5580,6 +5593,8 @@ void srt::CUDT::setInitialRcvSeq(int32_t isn) m_iRcvLastAckAck = isn; m_iRcvCurrSeqNo = CSeqNo::decseq(isn); + HLOGC(cnlog.Debug, log << "setInitialRcvSeq: ACK: %" << isn << " last-recv %" << CSeqNo::decseq(isn)); + #if ENABLE_NEW_RCVBUFFER sync::ScopedLock rb(m_RcvBufferLock); if (m_pRcvBuffer) @@ -5784,15 +5799,14 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& { #if ENABLE_BONDING - ScopedLock cl (uglobal().m_GlobControlLock); - CUDTGroup* g = m_parent->m_GroupOf; - if (g) + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); + if (gkeeper.group) { // This is the last moment when this can be done. // The updateAfterSrtHandshake call will copy the receiver // start time to the receiver buffer data, so the correct // value must be set before this happens. - synchronizeWithGroup(g); + synchronizeWithGroup(gkeeper.group); have_group = true; } #endif @@ -6187,6 +6201,11 @@ bool srt::CUDT::closeInternal() HLOGC(smlog.Debug, log << CONID() << "CLOSING STATE. Acquiring connection lock"); + // XXX m_ConnectionLock should preced m_GlobControlLock, + // so it could be a potential deadlock. Consider making sure that + // any potential connection processing is impossible on a socket + // that has m_bClosing flag set and so locking m_ConnectionLock is + // not necessary. ScopedLock connectguard(m_ConnectionLock); // Signal the sender and recver if they are waiting for data. @@ -7993,10 +8012,74 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp m_tsLastSndTime.store(steady_clock::now()); } +bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) +{ +// Only with "new bonding" try to extract this information from the group. +// Otherwise stay with the usual per-socket check. +#if ENABLE_NEW_BONDING + CUDTUnited::GroupKeeper gk(uglobal(), m_parent); + CUDTGroup* g = m_parent->m_GroupOf; + if (g) + { + return g->getFirstNoncontSequence((w_seq), (w_log_reason)); + } +#endif + +#if ENABLE_NEW_RCVBUFFER + + SRT_ASSERT(!!m_pRcvBuffer); + if (!m_pRcvBuffer) + { + LOGP(cnlog.Error, "IPE: ack can't be sent, buffer doesn't exist and no group membership"); + return false; + } + { + ScopedLock buflock (m_RcvBufferLock); + bool has_followers = m_pRcvBuffer->getContiguousEnd((w_seq)); + if (has_followers) + w_log_reason = "first lost"; + else + w_log_reason = "expected next"; + } +#else + + // NOTE: with the old receiver buffer, groups rely fully on single link internals. + // So this is to be done for both single-socket and groups. + + // With the old receiver buffer simply stay with the old procedure. + // Either you have the first loss, or you have no loss and therefore + // get the next-to-last-received. + { + // If there is no loss, the ACK is the current largest sequence number plus 1; + // Otherwise it is the smallest sequence number in the receiver loss list. + ScopedLock lock(m_RcvLossLock); + // TODO: Consider the Fresh Loss list as well!!! + // (actually it's considered already in the new rcv-buffer, + // so this problem will die naturally). + w_seq = m_pRcvLossList->getFirstLostSeq(); + } + + // We don't need to check the length prematurely, + // if length is 0, this will return SRT_SEQNO_NONE. + // If so happened, simply use the latest received pkt + 1. + if (w_seq == SRT_SEQNO_NONE) + { + w_seq = CSeqNo::incseq(m_iRcvCurrSeqNo); + w_log_reason = "expected next"; + } + else + { + w_log_reason = "first lost"; + } + +#endif + + return true; +} + int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { SRT_ASSERT(ctrlpkt.getMsgTimeStamp() != 0); - int32_t ack; // First unacknowledged packet seqnuence number (acknowledge up to ack). int nbsent = 0; int local_prevack = 0; @@ -8011,30 +8094,12 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) (void)l_saveback; // kill compiler warning: unused variable `l_saveback` [-Wunused-variable] local_prevack = m_iDebugPrevLastAck; - - string reason = "first lost"; // just for "a reason" of giving particular % for ACK #endif + string reason; // just for "a reason" of giving particular % for ACK -#if 0 // ENABLE_BONDING && ENABLE_NEW_RCVBUFFER - dropToGroupRecvBase(); -#endif - - { - // If there is no loss, the ACK is the current largest sequence number plus 1; - // Otherwise it is the smallest sequence number in the receiver loss list. - ScopedLock lock(m_RcvLossLock); - // TODO: Consider the Fresh Loss list as well!!! - ack = m_pRcvLossList->getFirstLostSeq(); - } - - // We don't need to check the length prematurely, - // if length is 0, this will return SRT_SEQNO_NONE. - // If so happened, simply use the latest received pkt + 1. - if (ack == SRT_SEQNO_NONE) - { - ack = CSeqNo::incseq(m_iRcvCurrSeqNo); - IF_HEAVY_LOGGING(reason = "expected next"); - } + int32_t ack; // First unacknowledged packet sequence number (acknowledge up to ack). + if (!getFirstNoncontSequence((ack), (reason))) + return nbsent; if (m_iRcvLastAckAck == ack) { @@ -8159,7 +8224,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { CUniqueSync rdcc (m_RecvLock, m_RecvDataCond); -#if ENABLE_NEW_RCVBUFFER +#if ENABLE_NEW_BONDING // Locks m_RcvBufferLock, which is unlocked above by InvertedLock un_bufflock. // Must check read-readiness under m_RecvLock to protect the epoll from concurrent changes in readBuffer() @@ -8170,7 +8235,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) // Formally, for safety this should rather check the existence of m_pRcvBuffer. SRT_ASSERT( bool(m_parent->m_GroupOf) != bool(m_pRcvBuffer) ); - if (!m_parent->m_GroupOf && isRcvBufferReady()) + if (m_pRcvBuffer && isRcvBufferReady()) #endif { if (m_config.bSynRecving) @@ -8227,7 +8292,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) else { // Not possible (m_iRcvCurrSeqNo+1 <% m_iRcvLastAck ?) - LOGC(xtlog.Error, log << "sendCtrl(UMSG_ACK): IPE: curr %" << ack + LOGC(xtlog.Error, log << "sendCtrl(UMSG_ACK): IPE: current (" << reason << ") %" << ack << " <% last %" << m_iRcvLastAck); return nbsent; } @@ -10281,9 +10346,6 @@ int srt::CUDT::checkLazySpawnLatencyThread() // for that case. SRT_ASSERT(!(m_bTsbPd && m_bGroupTsbPd)); - // Also, just in case, check if the socket is associated - // when group tsbpd is needed. - SRT_ASSERT(m_parent->m_GroupOf || !m_bGroupTsbPd); #else const bool need_tsbpd = m_bTsbPd; #endif @@ -10317,6 +10379,14 @@ int srt::CUDT::checkLazySpawnLatencyThread() #if ENABLE_NEW_BONDING if (need_group_tsbpd) { + ScopedLock glock(uglobal().m_GlobControlLock); + if (m_bClosing) + return -1; + + // Also, just in case, check if the socket is associated + // when group tsbpd is needed. + SRT_ASSERT(m_parent->m_GroupOf || !m_bGroupTsbPd); + // Shipped to the group function because this will // likely require groupwise locking. return m_parent->m_GroupOf->checkLazySpawnLatencyThread(); @@ -10326,6 +10396,464 @@ int srt::CUDT::checkLazySpawnLatencyThread() return 0; } +#if ENABLE_HEAVY_LOGGING +#if ENABLE_NEW_BONDING +CUDT::time_point srt::CUDT::getPacketPTS(CUDTGroup* grp, const CPacket& packet) +{ + steady_clock::time_point pts; + + // Block this for a case of new-bonding group, as m_pRcvBuffer is NULL there. + if (grp) + { + pts = grp->getPktTsbPdTime(packet.getMsgTimeStamp()); + } + else if (!m_pRcvBuffer) + { + // Somehow we have dispatched to a previous member socket, + // that was already removed from the group, which means that + // it is being closed now. Pretend nothing has been dispatched. + pts = steady_clock::time_point() + milliseconds_from(m_iTsbPdDelay_ms); + } + else + { + pts = m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); + } + + return pts; +} +#else +CUDT::time_point srt::CUDT::getPacketPTS(void*, const CPacket& packet) +{ + return m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); +} +#endif + +static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; + +#endif + +int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& w_new_inserted, steady_clock::time_point& w_next_tsbpd, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs) +{ + int initial_loss_ttl = 0; + if (m_bPeerRexmitFlag) + initial_loss_ttl = m_iReorderTolerance; + + bool excessive SRT_ATR_UNUSED = true; // stays true unless it was successfully added + + // Loop over all incoming packets that were filtered out. + // In case when there is no filter, there's just one packet in 'incoming', + // the one that came in the input of this function. + for (vector::const_iterator unitIt = incoming.begin(); unitIt != incoming.end(); ++unitIt) + { + CUnit * u = *unitIt; + CPacket &rpkt = u->m_Packet; + const int pktrexmitflag = m_bPeerRexmitFlag ? (rpkt.getRexmitFlag() ? 1 : 0) : 2; + + time_point pts = steady_clock::now() + milliseconds_from(m_iTsbPdDelay_ms); + IF_HEAVY_LOGGING(pts = getPacketPTS(NULL, rpkt)); + + int buffer_add_result; + bool adding_successful = true; + + // m_iRcvLastSkipAck is the base sequence number for the receiver buffer. + // This is the offset in the buffer; if this is negative, it means that + // this sequence is already in the past and the buffer is not interested. + // Meaning, this packet will be rejected, even if it could potentially be + // one of missing packets in the transmission. + int32_t offset = CSeqNo::seqoff(m_iRcvLastSkipAck, rpkt.m_iSeqNo); + + IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); + + if (offset < 0) + { + IF_HEAVY_LOGGING(exc_type = "BELATED"); + enterCS(m_StatsLock); + const double bltime = (double) CountIIR( + uint64_t(m_stats.traceBelatedTime) * 1000, + count_microseconds(steady_clock::now() - pts), 0.2); + + m_stats.traceBelatedTime = bltime / 1000.0; + m_stats.rcvr.recvdBelated.count(rpkt.getLength()); + leaveCS(m_StatsLock); + HLOGC(qrlog.Debug, + log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo << " offset=" << offset << " (BELATED/" + << rexmitstat[pktrexmitflag] << ") FLAGS: " << rpkt.MessageFlagStr()); + continue; + } + + int avail_bufsize = 0; // needed in logging + + // This is executed only when bonding is enabled and only + // with the new buffer (in which case the buffer is in the group). + + avail_bufsize = (int) getAvailRcvBufferSizeNoLock(); + if (offset >= avail_bufsize) + { + // This is already a sequence discrepancy. Probably there could be found + // some way to make it continue reception by overriding the sequence and + // make a kinda TLKPTDROP, but there has been found no reliable way to do this. + if (m_bTsbPd && m_bTLPktDrop && m_pRcvBuffer->empty()) + { + // Only in live mode. In File mode this shall not be possible + // because the sender should stop sending in this situation. + // In Live mode this means that there is a gap between the + // lowest sequence in the empty buffer and the incoming sequence + // that exceeds the buffer size. Receiving data in this situation + // is no longer possible and this is a point of no return. + + LOGC(qrlog.Error, log << CONID() << + "SEQUENCE DISCREPANCY. BREAKING CONNECTION." + " seq=" << rpkt.m_iSeqNo + << " buffer=(" << m_iRcvLastSkipAck + << ":" << m_iRcvCurrSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, int(m_pRcvBuffer->capacity()) - 1) + << "), " << (offset-avail_bufsize+1) + << " past max. Reception no longer possible. REQUESTING TO CLOSE."); + + return -2; + } + else + { +#if ENABLE_NEW_RCVBUFFER + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo + << ", insert offset " << offset << ". " + << m_pRcvBuffer->strFullnessState(m_iRcvLastAck, steady_clock::now()) + ); +#else + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo + << ", insert offset " << offset << ". " + << m_pRcvBuffer->strFullnessState(steady_clock::now()) + ); +#endif + + return -1; + } + } + +#if ENABLE_NEW_RCVBUFFER + CRcvBufferNew::InsertInfo info = m_pRcvBuffer->insert(u); + + // Remember this value in order to CHECK if there's a need + // to request triggering TSBPD in case when TSBPD is in the + // state of waiting forever and wants to know if there's any + // possible time to wake up known earlier than that. + + // Note that in case of the "builtin group reader" (its own + // buffer), there's no need to do it here because it has also + // its own TSBPD thread. + + if (info.result == CRcvBufferNew::InsertInfo::INSERTED) + { + // This may happen multiple times in the loop, so update only if earlier. + if (w_next_tsbpd == time_point() || w_next_tsbpd > info.first_time) + w_next_tsbpd = info.first_time; + w_new_inserted = true; + } + buffer_add_result = int(info.result); +#else + buffer_add_result = m_pRcvBuffer->addData(u, offset); + w_new_inserted = (buffer_add_result == 0); + w_next_tsbpd = time_point(); // We don't use this mechanism for the old buffer. +#endif + + if (buffer_add_result < 0) + { + // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. + // So this packet is "redundant". + IF_HEAVY_LOGGING(exc_type = "UNACKED"); + adding_successful = false; + } + else + { + IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); + excessive = false; + if (u->m_Packet.getMsgCryptoFlags() != EK_NOENC) + { + EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt((u->m_Packet)) : ENCS_NOTSUP; + if (rc != ENCS_CLEAR) + { + // Heavy log message because if seen once the message may happen very often. + HLOGC(qrlog.Debug, log << CONID() << "ERROR: packet not decrypted, dropping data."); + adding_successful = false; + IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); + + ScopedLock lg(m_StatsLock); + m_stats.rcvr.undecrypted.count(stats::BytesPackets(rpkt.getLength(), 1)); + } + } + } + + if (adding_successful) + { + ScopedLock statslock(m_StatsLock); + m_stats.rcvr.recvdUnique.count(u->m_Packet.getLength()); + } + +#if ENABLE_HEAVY_LOGGING + std::ostringstream expectspec; + if (excessive) + expectspec << "EXCESSIVE(" << exc_type << ")"; + else + expectspec << "ACCEPTED"; + + std::ostringstream bufinfo; + + if (m_pRcvBuffer) + { + bufinfo << " BUFr=" << avail_bufsize + << " avail=" << getAvailRcvBufferSizeNoLock() + << " buffer=(" << m_iRcvLastSkipAck + << ":" << m_iRcvCurrSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, m_pRcvBuffer->capacity()-1) + << ")"; + } + + // Empty buffer info in case of groupwise receiver. + // There's no way to obtain this information here. + + LOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo + << " offset=" << offset + << bufinfo.str() + << " RSL=" << expectspec.str() + << " SN=" << rexmitstat[pktrexmitflag] + << " FLAGS: " + << rpkt.MessageFlagStr()); +#endif + + // Decryption should have made the crypto flags EK_NOENC. + // Otherwise it's an error. + if (adding_successful) + { + HLOGC(qrlog.Debug, + log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo)); + + // Lame way to make an optional variable. + bool have_loss = false; + + if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. + { + int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); + int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); + + have_loss = true; + w_srt_loss_seqs.push_back(make_pair(seqlo, seqhi)); + HLOGC(qrlog.Debug, log << "pkt/LOSS DETECTED: %" << seqlo << " - %" << seqhi); + } + + if (initial_loss_ttl && have_loss) + { + // pack loss list for (possibly belated) NAK + // The LOSSREPORT will be sent in a while. + + ScopedLock lg(m_RcvLossLock); + for (loss_seqs_t::iterator i = w_srt_loss_seqs.begin(); i != w_srt_loss_seqs.end(); ++i) + { + m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); + HLOGC(qrlog.Debug, + log << "FreshLoss: added sequences: %(" << i->first << "-" << i->second + << ") tolerance: " << initial_loss_ttl); + } + } + } + + // Update the current largest sequence number that has been received. + // Or it is a retransmitted packet, remove it from receiver loss list. + // + // Group note: for the new group receiver the group hosts the receiver + // buffer, but the socket still maintains the losses. + if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvCurrSeqNo) > 0) + { + m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received + } + else + { + unlose(rpkt); // was BELATED or RETRANSMITTED + w_was_sent_in_order &= 0 != pktrexmitflag; + } + } + + return 0; +} + +#if ENABLE_NEW_BONDING +bool srt::CUDT::handleGroupPacketReception(CUDTGroup* grp, const vector& incoming, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs) +{ + bool excessive SRT_ATR_UNUSED = true; // stays true unless it was successfully added + + int initial_loss_ttl = 0; + if (m_bPeerRexmitFlag) + initial_loss_ttl = m_iReorderTolerance; + + time_point pts = steady_clock::now() + milliseconds_from(m_iTsbPdDelay_ms); + + // Loop over all incoming packets that were filtered out. + // In case when there is no filter, there's just one packet in 'incoming', + // the one that came in the input of CUDT::processData(). + for (vector::const_iterator unitIt = incoming.begin(); unitIt != incoming.end(); ++unitIt) + { + CUnit * u = *unitIt; + CPacket &rpkt = u->m_Packet; + const int pktrexmitflag = m_bPeerRexmitFlag ? (rpkt.getRexmitFlag() ? 1 : 0) : 2; + const int pktsz = (int) rpkt.getLength(); + + IF_HEAVY_LOGGING(pts = getPacketPTS(grp, rpkt)); + + bool adding_successful = true; + bool have_loss = false; + + // This is executed only when bonding is enabled and only + // with the new buffer (in which case the buffer is in the group). + // NOTE: this will lock ALSO the receiver buffer lock in the group + CRcvBufferNew::InsertInfo info = grp->addDataUnit(u, (w_srt_loss_seqs), (have_loss)); + + if (info.result == CRcvBufferNew::InsertInfo::DISCREPANCY) + { + // XXX PROBABLY the new receiver buffer can give the possibility + // of completely resetting itself at the moment when this happens, + // so closing may be not necessary in case of TLPKTDROP, but instead + // the whole buffer will be dropped and it will start over from the + // newly incoming sequence number. + if (m_bGroupTsbPd && info.avail_range == 0) + { + LOGC(qrlog.Error, log << CONID() << + "SEQUENCE DISCREPANCY. BREAKING CONNECTION."); + + // Here in this place there's nothing to unlock; locking is done + // exclusively in the call to addDataUnit(). + processClose(); + } + else + { + // Can't reach the buffer information because it's inside the group. + // The log should be likely fully presented in the CUDTGroup::addDataUnit(). + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo); + } + return false; + } + + IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); + + if (info.result == CRcvBufferNew::InsertInfo::BELATED) + { + IF_HEAVY_LOGGING(exc_type = "BELATED"); + enterCS(m_StatsLock); + const double bltime = (double) CountIIR( + uint64_t(m_stats.traceBelatedTime) * 1000, + count_microseconds(steady_clock::now() - pts), 0.2); + + m_stats.traceBelatedTime = bltime / 1000.0; + m_stats.rcvr.recvdBelated.count(rpkt.getLength()); + leaveCS(m_StatsLock); + HLOGC(qrlog.Debug, + log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo << " (BELATED/" + << rexmitstat[pktrexmitflag] << ") FLAGS: " << rpkt.MessageFlagStr()); + + // For BELATED packets you should just skip anything else. + // This means it's already beyond the first entry in the buffer, so this + // sequence means nothing also for the loss check. + continue; + } + + if (info.result == CRcvBufferNew::InsertInfo::REDUNDANT) + { + // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. + // So this packet is "redundant". + IF_HEAVY_LOGGING(exc_type = "UNACKED"); + adding_successful = false; + } + else // INSERTED + { + IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); + excessive = false; + if (u->m_Packet.getMsgCryptoFlags() != EK_NOENC) + { + EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt((u->m_Packet)) : ENCS_NOTSUP; + if (rc != ENCS_CLEAR) + { + // Heavy log message because if seen once the message may happen very often. + HLOGC(qrlog.Debug, log << CONID() << "ERROR: packet not decrypted, dropping data."); + adding_successful = false; + IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); + + ScopedLock lg(m_StatsLock); + m_stats.rcvr.undecrypted.count(stats::BytesPackets(pktsz, 1)); + } + } + } + + + // Decryption should have made the crypto flags EK_NOENC. + // Otherwise it's an error. + if (adding_successful) + { + HLOGC(qrlog.Debug, + log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo)); + + if (have_loss) + { + HLOGC(qrlog.Debug, log << "grp/LOSS DETECTED: " << FormatLossArray(w_srt_loss_seqs)); + } + + if (initial_loss_ttl && have_loss) + { + // pack loss list for (possibly belated) NAK + // The LOSSREPORT will be sent in a while. + + ScopedLock lg(m_RcvLossLock); + for (loss_seqs_t::iterator i = w_srt_loss_seqs.begin(); i != w_srt_loss_seqs.end(); ++i) + { + m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); + HLOGC(qrlog.Debug, + log << "FreshLoss: added sequences: %(" << i->first << "-" << i->second + << ") tolerance: " << initial_loss_ttl); + } + } + } + + + if (adding_successful) + { + ScopedLock statslock(m_StatsLock); + m_stats.rcvr.recvdUnique.count(u->m_Packet.getLength()); + } + +#if ENABLE_HEAVY_LOGGING + std::ostringstream expectspec; + if (excessive) + expectspec << "EXCESSIVE(" << exc_type << ")"; + else + expectspec << "ACCEPTED"; + + // Empty buffer info in case of groupwise receiver. + // There's no way to obtain this information here. + + LOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo + << " RSL=" << expectspec.str() + << " SN=" << rexmitstat[pktrexmitflag] + << " FLAGS: " + << rpkt.MessageFlagStr()); +#endif + + // Update the current largest sequence number that has been received. + // Or it is a retransmitted packet, remove it from receiver loss list. + // + // Group note: for the new group receiver the group hosts the receiver + // buffer, but the socket still maintains the losses. + if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvCurrSeqNo) > 0) + { + m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received + } + else + { + unlose(rpkt); // was BELATED or RETRANSMITTED + w_was_sent_in_order &= 0 != pktrexmitflag; + } + } + + return true; +} +#endif + int srt::CUDT::processData(CUnit* in_unit) { if (m_bClosing) @@ -10350,10 +10878,6 @@ int srt::CUDT::processData(CUnit* in_unit) } const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2; -#if ENABLE_HEAVY_LOGGING - static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; - string rexmit_reason; -#endif if (pktrexmitflag == 1) { @@ -10362,45 +10886,20 @@ int srt::CUDT::processData(CUnit* in_unit) m_stats.rcvr.recvdRetrans.count(packet.getLength()); leaveCS(m_StatsLock); -#if ENABLE_HEAVY_LOGGING - // Check if packet was retransmitted on request or on ack timeout - // Search the sequence in the loss record. - rexmit_reason = " by "; - if (!m_pRcvLossList->find(packet.m_iSeqNo, packet.m_iSeqNo)) - rexmit_reason += "BLIND"; - else - rexmit_reason += "NAKREPORT"; -#endif } steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) steady_clock::time_point pts = steady_clock::time_point() + tsbpddelay; #if ENABLE_HEAVY_LOGGING - { - // It's easier to remove the latency factor from this value than to add a function // that exposes the details basing on which this value is calculated. - -#if ENABLE_NEW_BONDING - // Block this for a case of new-bonding group, as m_pRcvBuffer is NULL there. - if (gkeeper.group) - { - pts = gkeeper.group->getPktTsbPdTime(packet.getMsgTimeStamp()); - } - else if (!m_pRcvBuffer) - { - // Somehow we have dispatched to a previous member socket, - // that was already removed from the group, which means that - // it is being closed now. Pretend nothing has been dispatched. - return -1; - } - else +#if ENABLE_BONDING + pts = getPacketPTS(gkeeper.group, packet); +#else + pts = getPacketPTS(NULL, packet); #endif - { - pts = m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); - } steady_clock::time_point ets = pts - tsbpddelay; HLOGC(qrlog.Debug, log << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() @@ -10447,7 +10946,6 @@ int srt::CUDT::processData(CUnit* in_unit) loss_seqs_t srt_loss_seqs; vector incoming; bool was_sent_in_order = true; - bool reorder_prevent_lossreport = false; // If the peer doesn't understand REXMIT flag, send rexmit request // always immediately. @@ -10541,316 +11039,96 @@ int srt::CUDT::processData(CUnit* in_unit) time_point next_tsbpd_avail; bool new_inserted = false; + if (m_PacketFilter) { - // Start of offset protected section - // Prevent TsbPd thread from modifying Ack position while adding data - // offset from RcvLastAck in RcvBuffer must remain valid between seqoff() and addData() - UniqueLock recvbuf_acklock(m_RcvBufferLock); - - // vector undec_units; - if (m_PacketFilter) - { - // Stuff this data into the filter - m_PacketFilter.receive(in_unit, (incoming), (filter_loss_seqs)); - HLOGC(qrlog.Debug, - log << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs) - << " loss to report, " - << (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF" - : "REPORT ONLY THOSE")); - } - else - { - // Stuff in just one packet that has come in. - incoming.push_back(in_unit); - } - - bool excessive = true; // stays true unless it was successfully added + // Stuff this data into the filter + m_PacketFilter.receive(in_unit, (incoming), (filter_loss_seqs)); + HLOGC(qrlog.Debug, + log << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs) + << " loss to report, " + << (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF" + : "REPORT ONLY THOSE")); + } + else + { + // Stuff in just one packet that has come in. + incoming.push_back(in_unit); + } +#if ENABLE_NEW_BONDING + if (gkeeper.group) + { // Needed for possibly check for needsQuickACK. bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.m_iSeqNo, m_iRcvLastSkipAck) < 0); - // Loop over all incoming packets that were filtered out. - // In case when there is no filter, there's just one packet in 'incoming', - // the one that came in the input of this function. - for (vector::iterator unitIt = incoming.begin(); unitIt != incoming.end(); ++unitIt) - { - CUnit * u = *unitIt; - CPacket &rpkt = u->m_Packet; - - int buffer_add_result; - bool adding_successful = true; - - // m_iRcvLastSkipAck is the base sequence number for the receiver buffer. - // This is the offset in the buffer; if this is negative, it means that - // this sequence is already in the past and the buffer is not interested. - // Meaning, this packet will be rejected, even if it could potentially be - // one of missing packets in the transmission. - int32_t offset = CSeqNo::seqoff(m_iRcvLastSkipAck, rpkt.m_iSeqNo); - - IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); - - if (offset < 0) - { - IF_HEAVY_LOGGING(exc_type = "BELATED"); - enterCS(m_StatsLock); - const double bltime = (double) CountIIR( - uint64_t(m_stats.traceBelatedTime) * 1000, - count_microseconds(steady_clock::now() - pts), 0.2); - - m_stats.traceBelatedTime = bltime / 1000.0; - m_stats.rcvr.recvdBelated.count(rpkt.getLength()); - leaveCS(m_StatsLock); - HLOGC(qrlog.Debug, - log << CONID() << "RECEIVED: seq=" << packet.m_iSeqNo << " offset=" << offset << " (BELATED/" - << rexmitstat[pktrexmitflag] << rexmit_reason << ") FLAGS: " << packet.MessageFlagStr()); - continue; - } - - int avail_bufsize = 0; // needed in logging - - // This is executed only when bonding is enabled and only - // with the new buffer (in which case the buffer is in the group). -#if ENABLE_NEW_BONDING - if (gkeeper.group) - { - // NOTE: this will lock ALSO the receiver buffer lock in the group - CRcvBufferNew::InsertInfo info = gkeeper.group->addDataUnit(u); - if (info.result == CRcvBufferNew::InsertInfo::DISCREPANCY) - { - // XXX PROBABLY the new receiver buffer can give the possibility - // of completely resetting itself at the moment when this happens, - // so closing may be not necessary in case of TLPKTDROP, but instead - // the whole buffer will be dropped and it will start over from the - // newly incoming sequence number. - if (m_bGroupTsbPd && info.avail_range == 0) - { - LOGC(qrlog.Error, log << CONID() << - "SEQUENCE DISCREPANCY. BREAKING CONNECTION."); - - recvbuf_acklock.unlock(); - processClose(); - } - else - { - // Can't reach the buffer information because it's inside the group. - // The log should be likely fully presented in the CUDTGroup::addDataUnit(). - LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo); - } - return -1; - } - - buffer_add_result = int(info.result); - } - else -#endif - { - avail_bufsize = (int) getAvailRcvBufferSizeNoLock(); - if (offset >= avail_bufsize) - { - // This is already a sequence discrepancy. Probably there could be found - // some way to make it continue reception by overriding the sequence and - // make a kinda TLKPTDROP, but there has been found no reliable way to do this. - if (m_bTsbPd && m_bTLPktDrop && m_pRcvBuffer->empty()) - { - // Only in live mode. In File mode this shall not be possible - // because the sender should stop sending in this situation. - // In Live mode this means that there is a gap between the - // lowest sequence in the empty buffer and the incoming sequence - // that exceeds the buffer size. Receiving data in this situation - // is no longer possible and this is a point of no return. - - LOGC(qrlog.Error, log << CONID() << - "SEQUENCE DISCREPANCY. BREAKING CONNECTION." - " seq=" << rpkt.m_iSeqNo - << " buffer=(" << m_iRcvLastSkipAck - << ":" << m_iRcvCurrSeqNo // -1 = size to last index - << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, int(m_pRcvBuffer->capacity()) - 1) - << "), " << (offset-avail_bufsize+1) - << " past max. Reception no longer possible. REQUESTING TO CLOSE."); - - // This is a scoped lock with AckLock, but for the moment - // when processClose() is called this lock must be taken out, - // otherwise this will cause a deadlock. We don't need this - // lock anymore, and at 'return' it will be unlocked anyway. - recvbuf_acklock.unlock(); - processClose(); - return -1; - } - else - { -#if ENABLE_NEW_RCVBUFFER - LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo - << ", insert offset " << offset << ". " - << m_pRcvBuffer->strFullnessState(m_iRcvLastAck, steady_clock::now()) - ); -#else - LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo - << ", insert offset " << offset << ". " - << m_pRcvBuffer->strFullnessState(steady_clock::now()) - ); -#endif - - return -1; - } - } - -#if ENABLE_NEW_RCVBUFFER - CRcvBufferNew::InsertInfo info = m_pRcvBuffer->insert(u); - - // Remember this value in order to CHECK if there's a need - // to request triggering TSBPD in case when TSBPD is in the - // state of waiting forever and wants to know if there's any - // possible time to wake up known earlier than that. - - // Note that in case of the "builtin group reader" (its own - // buffer), there's no need to do it here because it has also - // its own TSBPD thread. - if (info.result == CRcvBufferNew::InsertInfo::INSERTED) - { - // This may happen multiple times in the loop, so update only if earlier. - if (next_tsbpd_avail == time_point() || next_tsbpd_avail > info.first_time) - next_tsbpd_avail = info.first_time; - new_inserted = true; - } - buffer_add_result = int(info.result); -#else - buffer_add_result = m_pRcvBuffer->addData(u, offset); -#endif - } + bool handled = handleGroupPacketReception(gkeeper.group, + incoming, + (was_sent_in_order), + (srt_loss_seqs)); - if (buffer_add_result < 0) - { - // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. - // So this packet is "redundant". - IF_HEAVY_LOGGING(exc_type = "UNACKED"); - adding_successful = false; - } - else - { - IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); - excessive = false; - if (u->m_Packet.getMsgCryptoFlags() != EK_NOENC) - { - EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt((u->m_Packet)) : ENCS_NOTSUP; - if (rc != ENCS_CLEAR) - { - // Heavy log message because if seen once the message may happen very often. - HLOGC(qrlog.Debug, log << CONID() << "ERROR: packet not decrypted, dropping data."); - adding_successful = false; - IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); - - ScopedLock lg(m_StatsLock); - m_stats.rcvr.undecrypted.count(stats::BytesPackets(pktsz, 1)); - } - } - } + // These variables are used to decide about pinging the + // CV that kicks the TSBPD thread prematurely. In case of + // group common receiver there's no per-socket TSBPD and + // the group TSBPD will be handled internally. + next_tsbpd_avail = time_point(); + new_inserted = false; - if (adding_successful) - { - ScopedLock statslock(m_StatsLock); - m_stats.rcvr.recvdUnique.count(u->m_Packet.getLength()); - } - -#if ENABLE_HEAVY_LOGGING - std::ostringstream expectspec; - if (excessive) - expectspec << "EXCESSIVE(" << exc_type << rexmit_reason << ")"; - else - expectspec << "ACCEPTED"; - - std::ostringstream bufinfo; + if (!handled) + return -1; - if (m_pRcvBuffer) + // This is moved earlier after introducing filter because it shouldn't + // be executed in case when the packet was rejected by the receiver buffer. + // However now the 'excessive' condition may be true also in case when + // a truly non-excessive packet has been received, just it has been temporarily + // stored for better times by the filter module. This way 'excessive' is also true, + // although the old condition that a packet with a newer sequence number has arrived + // or arrived out of order may still be satisfied. + if (!incoming_belated && was_sent_in_order) + { + // Basing on some special case in the packet, it might be required + // to enforce sending ACK immediately (earlier than normally after + // a given period). + if (m_CongCtl->needsQuickACK(packet)) { - bufinfo << " BUFr=" << avail_bufsize - << " avail=" << getAvailRcvBufferSizeNoLock() - << " buffer=(" << m_iRcvLastSkipAck - << ":" << m_iRcvCurrSeqNo // -1 = size to last index - << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, m_pRcvBuffer->capacity()-1) - << ")"; + m_tsNextACKTime.store(steady_clock::now()); } + } - // Empty buffer info in case of groupwise receiver. - // There's no way to obtain this information here. - - LOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo - << " offset=" << offset - << bufinfo.str() - << " RSL=" << expectspec.str() - << " SN=" << rexmitstat[pktrexmitflag] - << " FLAGS: " - << rpkt.MessageFlagStr()); -#endif - - // Decryption should have made the crypto flags EK_NOENC. - // Otherwise it's an error. - if (adding_successful) - { - HLOGC(qrlog.Debug, - log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo)); - - bool handled = false; - - // Lame way to make an optional variable. - bool have_loss = false; - -#if ENABLE_NEW_RCVBUFFER - if (gkeeper.group) - { - // This should take out assymmetric losses, and leave only - // valid ones. This also reports the losses to the group. - have_loss = gkeeper.group->checkPacketArrivalLoss(rpkt, (srt_loss_seqs)); - handled = true; - - if (have_loss) - { - HLOGC(qrlog.Debug, log << "grp/LOSS DETECTED: " << FormatLossArray(srt_loss_seqs)); - } - } + // Here continue the processing because even if no new packets were + // added to the buffer, there might be needed losses handled. + } + else #endif + { + // Start of offset protected section + // Prevent TsbPd thread from modifying Ack position while adding data + // offset from RcvLastAck in RcvBuffer must remain valid between seqoff() and addData() + UniqueLock recvbuf_acklock(m_RcvBufferLock); + // Needed for possibly check for needsQuickACK. + bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.m_iSeqNo, m_iRcvLastSkipAck) < 0); - if (!handled && CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. - { - int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); - int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); - - have_loss = true; - srt_loss_seqs.push_back(make_pair(seqlo, seqhi)); - } - - if (initial_loss_ttl && have_loss) - { - // pack loss list for (possibly belated) NAK - // The LOSSREPORT will be sent in a while. + int res = handleSocketPacketReception(incoming, + (new_inserted), + (next_tsbpd_avail), + (was_sent_in_order), + (srt_loss_seqs)); - reorder_prevent_lossreport = true; + if (res == -2) + { + // This is a scoped lock with AckLock, but for the moment + // when processClose() is called this lock must be taken out, + // otherwise this will cause a deadlock. We don't need this + // lock anymore, and at 'return' it will be unlocked anyway. + recvbuf_acklock.unlock(); + processClose(); - ScopedLock lg(m_RcvLossLock); - for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i) - { - m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); - HLOGC(qrlog.Debug, - log << "FreshLoss: added sequences: %(" << i->first << "-" << i->second - << ") tolerance: " << initial_loss_ttl); - } - } - } + return -1; + } - // Update the current largest sequence number that has been received. - // Or it is a retransmitted packet, remove it from receiver loss list. - // - // Group note: for the new group receiver the group hosts the receiver - // buffer, but the socket still maintains the losses. - if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvCurrSeqNo) > 0) - { - m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received - } - else - { - unlose(rpkt); // was BELATED or RETRANSMITTED - was_sent_in_order &= 0 != pktrexmitflag; - } + if (res == -1) + { + return -1; } // This is moved earlier after introducing filter because it shouldn't @@ -10871,11 +11149,11 @@ int srt::CUDT::processData(CUnit* in_unit) } } - if (excessive) + if (!new_inserted) { return -1; } - } // End of recvbuf_acklock + } if (m_bClosing) { @@ -10898,8 +11176,10 @@ int srt::CUDT::processData(CUnit* in_unit) // delivery (after drop), this time we have received a packet to be delivered // earlier than that, so we need to notify TSBPD immediately so that it // updates this itself, not sleep until the previously set time. - if ((m_bWakeOnRecv && new_inserted) || next_tsbpd_avail != time_point()) + if (m_bTsbPd && ((m_bWakeOnRecv && new_inserted) || next_tsbpd_avail != time_point())) { + HLOGC(qrlog.Debug, log << "processData: will SIGNAL TSBPD for socket. WakeOnRecv=" << m_bWakeOnRecv + << " new_inserted=" << new_inserted << " next_tsbpd_avail=" << FormatTime(next_tsbpd_avail)); CUniqueSync tsbpd_cc(m_RecvLock, m_RcvTsbPdCond); tsbpd_cc.notify_all(); } @@ -10930,7 +11210,7 @@ int srt::CUDT::processData(CUnit* in_unit) } const bool report_recorded_loss = !m_PacketFilter || m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS; - if (!reorder_prevent_lossreport && report_recorded_loss) + if (initial_loss_ttl == 0 && report_recorded_loss) { HLOGC(qrlog.Debug, log << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs)); sendLossReport(srt_loss_seqs); @@ -11136,6 +11416,8 @@ void srt::CUDT::updateIdleLinkFrom(CUDT* source) // XXX This function is currently unused. It should be fixed and put into use. // See the blocked call in CUDT::processData(). // XXX REVIEW LOCKS WHEN REACTIVATING! + +/* srt::CUDT::loss_seqs_t srt::CUDT::defaultPacketArrival(void* vself, CPacket& pkt) { // [[using affinity(m_pRcvBuffer->workerThread())]]; @@ -11194,6 +11476,7 @@ srt::CUDT::loss_seqs_t srt::CUDT::defaultPacketArrival(void* vself, CPacket& pkt return output; } +*/ #endif /// This function is called when a packet has arrived, which was behind the current @@ -11839,7 +12122,10 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) * not knowing what to retransmit when the only NAK sent by receiver is lost, * all packets past last ACK are retransmitted (rexmitMethod() == SRM_FASTREXMIT). */ + enterCS(m_RcvLossLock); const int loss_len = m_pRcvLossList->getLossLength(); + leaveCS(m_RcvLossLock); + SRT_ASSERT(loss_len >= 0); int debug_decision = BECAUSE_NO_REASON; @@ -12361,7 +12647,7 @@ bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShak return true; } -void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival) +void srt::CUDT::processKeepalive(const CPacket& ctrlpkt SRT_ATR_UNUSED, const time_point& tsArrival SRT_ATR_UNUSED) { // Here can be handled some protocol definition // for extra data sent through keepalive. diff --git a/srtcore/core.h b/srtcore/core.h index 6cf119b80..f48d4658a 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -669,6 +669,7 @@ class CUDT #if ENABLE_NEW_BONDING void skipMemberLoss(int32_t seqno); #endif + bool getFirstNoncontSequence(int32_t& w_seq, std::string& w_log_reason); void checkSndTimers(Whether2RegenKm regen = DONT_REGEN_KM); void handshakeDone() @@ -1072,6 +1073,18 @@ class CUDT void removeSndLossUpTo(int32_t seq); int processData(CUnit* unit); + int handleSocketPacketReception(const std::vector& incoming, bool& w_new_inserted, sync::steady_clock::time_point& w_next_tsbpd, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); + +#if ENABLE_NEW_BONDING + bool handleGroupPacketReception(CUDTGroup* grp, const std::vector& incoming, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); +#endif +#if ENABLE_HEAVY_LOGGING +#if ENABLE_NEW_BONDING + time_point getPacketPTS(CUDTGroup* grp, const CPacket& packet); +#else + time_point getPacketPTS(void* grp, const CPacket& packet); +#endif +#endif int checkLazySpawnLatencyThread(); void processClose(); diff --git a/srtcore/group.cpp b/srtcore/group.cpp index fc544b3ae..c7b048175 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -259,7 +259,9 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) , m_listener() #endif , m_iBusy() +#if ENABLE_NEW_RCVBUFFER , m_iRcvPossibleLossSeq(SRT_SEQNO_NONE) +#endif , m_iSndOldestMsgNo(SRT_MSGNO_NONE) , m_iSndAckedMsgNo(SRT_MSGNO_NONE) , m_uOPT_MinStabilityTimeout_us(1000 * CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS) @@ -293,8 +295,13 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) #endif { setupMutex(m_GroupLock, "Group"); - setupMutex(m_RcvDataLock, "RcvData"); - setupCond(m_RcvDataCond, "RcvData"); + setupMutex(m_RcvDataLock, "G/RcvData"); + setupCond(m_RcvDataCond, "G/RcvData"); +#if ENABLE_NEW_RCVBUFFER + setupCond(m_RcvTsbPdCond, "G/TSBPD"); + setupMutex(m_RcvBufferLock, "G/Buffer"); +#endif + #if !ENABLE_NEW_RCVBUFFER m_RcvEID = m_Global.m_EPoll.create(&m_RcvEpolld); #endif @@ -993,16 +1000,22 @@ void CUDTGroup::syncWithFirstSocket(const CUDT& core, const HandshakeSide side) #if ENABLE_NEW_RCVBUFFER -CRcvBufferNew::InsertInfo CUDTGroup::addDataUnit(CUnit* u) +CRcvBufferNew::InsertInfo CUDTGroup::addDataUnit(CUnit* u, CUDT::loss_seqs_t& w_losses, bool& w_have_loss) { // If this returns false, the adding has failed and CRcvBufferNew::InsertInfo info; const CPacket& rpkt = u->m_Packet; + w_have_loss = false; { ScopedLock lk (m_RcvBufferLock); info = m_pRcvBuffer->insert(u); + + if (info.result == CRcvBufferNew::InsertInfo::INSERTED) + { + w_have_loss = checkPacketArrivalLoss(u->m_Packet, (w_losses)); + } } if (info.result == CRcvBufferNew::InsertInfo::INSERTED) @@ -1010,9 +1023,10 @@ CRcvBufferNew::InsertInfo CUDTGroup::addDataUnit(CUnit* u) // If m_bTsbpdWaitForNewPacket, then notify anyway. // Otherwise notify only if a "fresher" packet was added, // so TSBPD should interrupt its sleep earlier and re-check. - if (m_bTsbpdWaitForNewPacket || info.first_time != time_point()) + if (m_bTsbPd && (m_bTsbpdWaitForNewPacket || info.first_time != time_point())) { - HLOGC(gmlog.Debug, log << "grp/addDataUnit: got a packet [live], SIGNAL TSBPD"); + HLOGC(gmlog.Debug, log << CONID() << "grp/addDataUnit: got a packet [live], reason:" + << (m_bTsbpdWaitForNewPacket ? "expected" : "sealing") << " - SIGNAL TSBPD"); // Make a lock on data reception first, to protect the buffer. // Then notify TSBPD if required. CUniqueSync tsbpd_cc(m_RcvDataLock, m_RcvTsbPdCond); @@ -1021,7 +1035,7 @@ CRcvBufferNew::InsertInfo CUDTGroup::addDataUnit(CUnit* u) } else if (info.result == CRcvBufferNew::InsertInfo::DISCREPANCY) { - LOGC(qrlog.Error, log << CONID() + LOGC(qrlog.Error, log << CONID() << "grp/addDataUnit: " << "SEQUENCE DISCREPANCY. DISCARDING." << " seq=" << rpkt.m_iSeqNo << " buffer=(" << m_pRcvBuffer->getStartSeqNo() @@ -1029,6 +1043,20 @@ CRcvBufferNew::InsertInfo CUDTGroup::addDataUnit(CUnit* u) << "+" << CSeqNo::incseq(m_pRcvBuffer->getStartSeqNo(), int(m_pRcvBuffer->capacity()) - 1) << ")"); } + else + { +#if ENABLE_HEAVY_LOGGING + static const char* const ival [] = { "inserted", "redundant", "belated", "discrepancy" }; + if (int(info.result) > -4 && int(info.result) <= 0) + { + LOGC(qrlog.Debug, log << CONID() << "grp/addDataUnit: insert status: " << ival[-info.result]); + } + else + { + LOGC(qrlog.Debug, log << CONID() << "grp/addDataUnit: IPE: invalid insert status"); + } +#endif + } return info; } @@ -1047,6 +1075,7 @@ int CUDTGroup::rcvDropTooLateUpTo(int seqno) if (CSeqNo::seqcmp(seqno, last_seq) > 0) seqno = last_seq; + // Skipping the sequence number of the new contiguous region iDropCnt = m_pRcvBuffer->dropUpTo(seqno); /* not sure how to stats. @@ -1061,21 +1090,21 @@ int CUDTGroup::rcvDropTooLateUpTo(int seqno) */ } - // Update every member's loss lists - { - ScopedLock lk (m_GroupLock); - - for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) - { - CUDT& u = gi->ps->core(); - u.skipMemberLoss(seqno); - } - } - return iDropCnt; } +void CUDTGroup::synchronizeLoss(int32_t seqno) +{ + ScopedLock lk (m_GroupLock); + + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + CUDT& u = gi->ps->core(); + u.skipMemberLoss(seqno); + } +} +// [[using locked(m_RcvBufferLock)]] bool CUDTGroup::checkPacketArrivalLoss(const CPacket& rpkt, CUDT::loss_seqs_t& w_losses) { // This is called when the packet was added to the buffer and this @@ -1093,26 +1122,33 @@ bool CUDTGroup::checkPacketArrivalLoss(const CPacket& rpkt, CUDT::loss_seqs_t& w // m_RcvLastSeqNo is atomic, so no need to protect it, // but it's also being modified using R-M-W method, and // this can potentially be interleft. - ScopedLock gl (m_GroupLock); + // + // Also, if this packet is going to be sealed from another + // socket in the group, then this check should be done again + // from the beginning, regarding the already recorded loss candidate. + + int32_t expected_seqno = m_RcvLastSeqNo; + expected_seqno = CSeqNo::incseq(expected_seqno); // For balancing groups, use some more complicated mechanism. if (type() == SRT_GTYPE_BALANCING || type() == SRT_GTYPE_BROADCAST) { have = checkBalancingLoss(rpkt, (w_losses)); } - else if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_RcvLastSeqNo)) > 0) + else if (CSeqNo::seqcmp(rpkt.m_iSeqNo, expected_seqno) > 0) { - int32_t seqlo = CSeqNo::incseq(m_RcvLastSeqNo); + int32_t seqlo = expected_seqno; int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); w_losses.push_back(make_pair(seqlo, seqhi)); have = true; - HLOGC(gmlog.Debug, log << "grp:checkPacketArrivalLoss: loss detected: %(" + HLOGC(grlog.Debug, log << "grp:checkPacketArrivalLoss: loss detected: %(" << seqlo << " - " << seqhi << ")"); } if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_RcvLastSeqNo) > 0) { + HLOGC(grlog.Debug, log << "grp:checkPacketArrivalLoss: latest updated: %" << m_RcvLastSeqNo << " -> %" << rpkt.m_iSeqNo); m_RcvLastSeqNo = rpkt.m_iSeqNo; } @@ -1131,12 +1167,16 @@ struct FFringeGreaterThan } }; +// [[using locked(m_RcvBufferLock)]] bool CUDTGroup::checkBalancingLoss(const CPacket& pkt, CUDT::loss_seqs_t& w_losses) { // This is done in case of every incoming packet. if (pkt.getSeqNo() == m_iRcvPossibleLossSeq) { + // XXX WARNING: it's unknown so far as to whether this "first loss" + // hasn't been reported already. + // This seals the exact loss position. // The returned value can be also NONE, which clears out the loss information. m_iRcvPossibleLossSeq = m_pRcvBuffer->getFirstLossSeq(m_iRcvPossibleLossSeq); @@ -1184,6 +1224,12 @@ bool CUDTGroup::checkBalancingLoss(const CPacket& pkt, CUDT::loss_seqs_t& w_loss // Extract the whole first loss typename CUDT::loss_seqs_t::value_type loss; loss.first = m_pRcvBuffer->getFirstLossSeq(m_iRcvPossibleLossSeq, (&loss.second)); + if (loss.first == SRT_SEQNO_NONE) + { + HLOGC(gmlog.Debug, log << "... LOSS SEALED (IPE) ???"); + m_iRcvPossibleLossSeq = SRT_SEQNO_NONE; + break; + } w_losses.push_back(loss); found_reportable_losses = true; @@ -1243,6 +1289,18 @@ bool CUDTGroup::checkBalancingLoss(const CPacket& pkt, CUDT::loss_seqs_t& w_loss return found_reportable_losses; } +bool CUDTGroup::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) +{ + ScopedLock buflock (m_RcvBufferLock); + bool has_followers = m_pRcvBuffer->getContiguousEnd((w_seq)); + if (has_followers) + w_log_reason = "first lost"; + else + w_log_reason = "last received"; + + return true; +} + #endif void CUDTGroup::close() @@ -1268,6 +1326,12 @@ void CUDTGroup::close() HLOGC(smlog.Debug, log << "group/close: IPE(NF): group member @" << ig->id << " already deleted"); continue; } + + // XXX This is not true in case of non-managed groups, which + // only collect sockets, but also non-managed groups should not + // use common group buffering and tsbpd. + s->setClosing(); + s->m_GroupOf = NULL; s->m_GroupMemberData = NULL; HLOGC(smlog.Debug, log << "group/close: CUTTING OFF @" << ig->id << " (found as @" << s->m_SocketID << ") from the group"); @@ -2700,12 +2764,14 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) HLOGC(arlog.Debug, log << CONID() << "grp:recv: CONNECTION BROKEN - reading from recv buffer just for formality"); int as_result = 0; - bool ready = m_pRcvBuffer->isRcvDataReady(steady_clock::now()); - - if (ready) { ScopedLock lk (m_RcvBufferLock); - as_result = m_pRcvBuffer->readMessage(data, len, (&w_mctrl)); + bool ready = m_pRcvBuffer->isRcvDataReady(steady_clock::now()); + + if (ready) + { + as_result = m_pRcvBuffer->readMessage(data, len, (&w_mctrl)); + } } { @@ -2751,12 +2817,14 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) HLOGC(arlog.Debug, log << CONID() << "grp:recv: BEGIN ASYNC MODE. Going to extract payload size=" << len); int as_result = 0; - bool ready = m_pRcvBuffer->isRcvDataReady(steady_clock::now()); - - if (ready) { ScopedLock lk (m_RcvBufferLock); - as_result = m_pRcvBuffer->readMessage(data, len, (&w_mctrl), (&seqrange)); + bool ready = m_pRcvBuffer->isRcvDataReady(steady_clock::now()); + + if (ready) + { + as_result = m_pRcvBuffer->readMessage(data, len, (&w_mctrl), (&seqrange)); + } } { @@ -2819,7 +2887,7 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) do { - if (stillConnected() && !timeout && !m_pRcvBuffer->isRcvDataReady(steady_clock::now())) + if (stillConnected() && !timeout && !isRcvBufferReady()) { /* Kick TsbPd thread to schedule next wakeup (if running) */ if (m_bTsbPd) @@ -6372,6 +6440,8 @@ void* CUDTGroup::tsbpd(void* param) << " ready=" << is_time_to_deliver << " ondrop=" << info.seq_gap); + bool synch_loss_after_drop = false; + if (!self->m_bTLPktDrop) { rxready = !info.seq_gap && is_time_to_deliver; @@ -6382,8 +6452,13 @@ void* CUDTGroup::tsbpd(void* param) if (info.seq_gap) { const int iDropCnt SRT_ATR_UNUSED = self->rcvDropTooLateUpTo(info.seqno); -#if ENABLE_LOGGING + + // Part required for synchronizing loss state in all group members should + // follow the drop, but this must be done outside the lock on the buffer. + synch_loss_after_drop = iDropCnt; + const int64_t timediff_us = count_microseconds(tnow - info.tsbpd_time); + #if ENABLE_HEAVY_LOGGING HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: DROPSEQ: up to seqno %" << CSeqNo::decseq(info.seqno) << " (" @@ -6395,13 +6470,15 @@ void* CUDTGroup::tsbpd(void* param) log << self->CONID() << "RCV-DROPPED " << iDropCnt << " packet(s). Packet seqno %" << info.seqno << " delayed for " << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') << (timediff_us % 1000) << " ms"); -#endif tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } } leaveCS(self->m_RcvBufferLock); + if (synch_loss_after_drop) + self->synchronizeLoss(info.seqno); + if (rxready) { HLOGC(tslog.Debug, diff --git a/srtcore/group.h b/srtcore/group.h index 97da7825e..638dcf9f2 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -670,7 +670,7 @@ class CUDTGroup }; friend ScopedGroupKeeper; - int32_t m_iRcvPossibleLossSeq; + sync::atomic m_iRcvPossibleLossSeq; #endif @@ -915,11 +915,13 @@ class CUDTGroup #if ENABLE_NEW_RCVBUFFER int checkLazySpawnLatencyThread(); - CRcvBufferNew::InsertInfo addDataUnit(CUnit* u); + CRcvBufferNew::InsertInfo addDataUnit(CUnit* u, CUDT::loss_seqs_t&, bool&); bool checkPacketArrivalLoss(const CPacket& rpkt, CUDT::loss_seqs_t&); bool checkBalancingLoss(const CPacket& rpkt, CUDT::loss_seqs_t&); - int rcvDropTooLateUpTo(int seqno); + int rcvDropTooLateUpTo(int32_t seqno); + void synchronizeLoss(int32_t seqno); void addGroupDriftSample(uint32_t timestamp, const time_point& tsArrival, int rtt); + bool getFirstNoncontSequence(int32_t& w_seq, std::string& w_log_reason); #endif bool applyGroupTime(time_point& w_start_time, time_point& w_peer_start_time) diff --git a/srtcore/list.cpp b/srtcore/list.cpp index 657653211..c47065cfe 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -656,6 +656,7 @@ srt::CRcvLossList::~CRcvLossList() void srt::CRcvLossList::insert(int32_t seqno1, int32_t seqno2) { + SRT_ASSERT(seqno1 != SRT_SEQNO_NONE && seqno2 != SRT_SEQNO_NONE); // Make sure that seqno2 isn't earlier than seqno1. SRT_ASSERT(CSeqNo::seqcmp(seqno1, seqno2) <= 0); diff --git a/srtcore/srt_attr_defs.h b/srtcore/srt_attr_defs.h index 6959e7775..7cc523f6e 100644 --- a/srtcore/srt_attr_defs.h +++ b/srtcore/srt_attr_defs.h @@ -85,18 +85,8 @@ used by SRT library internally. // Macro shortcut for implementing parts of the redundancy features // that require the new receiver buffer -#if defined(ENABLE_BONDING) - #if defined(ENABLE_NEW_RCVBUFFER) - #define ENABLE_NEW_BONDING 1 - #define ENABLE_OLD_BONDING 0 - #else - #define ENABLE_OLD_BONDING 1 - #define ENABLE_NEW_BONDING 0 - #endif -#else - #define ENABLE_NEW_BONDING 0 - #define ENABLE_OLD_BONDING 0 -#endif +#define ENABLE_NEW_BONDING (ENABLE_BONDING && ENABLE_NEW_RCVBUFFER) +#define ENABLE_OLD_BONDING (ENABLE_BONDING && !ENABLE_NEW_RCVBUFFER) /////////////////////////////////////////////////////////////////////////////// // Attributes for thread safety analysis From 2de148ef9bf87a69ba2b185ea1eab02c050cd316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 22 Sep 2022 12:11:41 +0200 Subject: [PATCH 14/62] Cosmetics --- srtcore/core.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 98c0f4339..7e744ba27 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -6201,7 +6201,7 @@ bool srt::CUDT::closeInternal() HLOGC(smlog.Debug, log << CONID() << "CLOSING STATE. Acquiring connection lock"); - // XXX m_ConnectionLock should preced m_GlobControlLock, + // XXX m_ConnectionLock should precede m_GlobControlLock, // so it could be a potential deadlock. Consider making sure that // any potential connection processing is impossible on a socket // that has m_bClosing flag set and so locking m_ConnectionLock is @@ -9802,7 +9802,7 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime } else if (ss.type == groups::SQT_LOSS) { - // This is scheduled retransmission request. The loss list may + // This is a scheduled retransmission request. The loss list may // contain more packets specs than this socket needs to retransmit, // as all sender loss lists should be synchronized in the group. From d07e71af8fb614ff8d4199014d51f00a68be9dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 22 Sep 2022 14:34:14 +0200 Subject: [PATCH 15/62] Minor bugfixes. Some line reordering and comment fixes --- srtcore/buffer.cpp | 2 +- srtcore/buffer_rcv.cpp | 27 ++----------- srtcore/core.cpp | 90 +++++++++++++++++++++++++++++++----------- 3 files changed, 71 insertions(+), 48 deletions(-) diff --git a/srtcore/buffer.cpp b/srtcore/buffer.cpp index a3ac73fdd..20149446f 100644 --- a/srtcore/buffer.cpp +++ b/srtcore/buffer.cpp @@ -310,7 +310,7 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) 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; diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index c23f3382b..ac1c34012 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -73,7 +73,6 @@ namespace { CRcvBufferNew::CRcvBufferNew(int initSeqNo, size_t size, /*CUnitQueue* unitqueue, */ bool bMessageAPI) : m_entries(size) , m_szSize(size) // TODO: maybe just use m_entries.size() - //, m_pUnitQueue(unitqueue) , m_iStartSeqNo(initSeqNo) // NOTE: SRT_SEQNO_NONE is allowed here. , m_iStartPos(0) , m_iEndPos(0) @@ -470,13 +469,9 @@ int CRcvBufferNew::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) bool needUpdateNonreadPos = (minDroppedOffset != -1 && minDroppedOffset <= getRcvDataSize()); releaseNextFillerEntries(); - // Required update after shifted m_iStartPos - if (cmpPos(m_iDropPos, m_iStartPos) < 0) - { - // Start from here and search fort the next gap - m_iEndPos = m_iDropPos = m_iStartSeqNo; - updateGapInfo(end_pos); - } + // Start from here and search fort the next gap + m_iEndPos = m_iDropPos = m_iStartSeqNo; + updateGapInfo(end_pos); if (needUpdateNonreadPos) { @@ -915,19 +910,6 @@ int CRcvBufferNew::getRcvDataSize(int& bytes, int& timespan) const CRcvBufferNew::PacketInfo CRcvBufferNew::getFirstValidPacketInfo() const { - /* - const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); - for (int i = m_iStartPos; i != end_pos; i = incPos(i)) - { - // TODO: Maybe check status? - if (!m_entries[i].pUnit) - continue; - - const CPacket& packet = m_entries[i].pUnit->m_Packet; - const PacketInfo info = { packet.getSeqNo(), i != m_iStartPos, getPktTsbPdTime(packet.getMsgTimeStamp()) }; - return info; - } - */ // Check the state of the very first packet first if (m_entries[m_iStartPos].status == EntryState_Avail) { @@ -941,8 +923,7 @@ CRcvBufferNew::PacketInfo CRcvBufferNew::getFirstValidPacketInfo() const return (PacketInfo) { pkt.getSeqNo(), true, getPktTsbPdTime(pkt.getMsgTimeStamp()) }; } - const PacketInfo info = { -1, false, time_point() }; - return info; + return (PacketInfo) { SRT_SEQNO_NONE, false, time_point() }; } std::pair CRcvBufferNew::getAvailablePacketsRange() const diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 7e744ba27..8fdddf212 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -295,7 +295,7 @@ void srt::CUDT::construct() m_bPeerTsbPd = false; m_iPeerTsbPdDelay_ms = 0; m_bTsbPd = false; - m_bWakeOnRecv = false; + m_bWakeOnRecv = false; m_bGroupTsbPd = false; m_bPeerTLPktDrop = false; @@ -1290,8 +1290,6 @@ size_t srt::CUDT::fillHsExtGroup(uint32_t* pcmdspec) // Note: if agent is a listener, and the current version supports // both sync methods, this flag might have been changed according to // the wish of the caller. - // if (m_parent->m_GroupOf->synconmsgno()) - // flags |= SRT_GFLAG_SYNCONMSG; // NOTE: this code remains as is for historical reasons. // The initial implementation stated that the peer id be @@ -5289,6 +5287,48 @@ void * srt::CUDT::tsbpd(void* param) * Set EPOLL_IN to wakeup any thread waiting on epoll */ self->uglobal().m_EPoll.update_events(self->m_SocketID, self->m_sPollID, SRT_EPOLL_IN, true); +#if ENABLE_OLD_BONDING + // If this is NULL, it means: + // - the socket never was a group member + // - the socket was a group member, but: + // - was just removed as a part of closure + // - and will never be member of the group anymore + + // If this is not NULL, it means: + // - This socket is currently member of the group + // - This socket WAS a member of the group, though possibly removed from it already, BUT: + // - the group that this socket IS OR WAS member of is in the GroupKeeper + // - the GroupKeeper prevents the group from being deleted + // - it is then completely safe to access the group here, + // EVEN IF THE SOCKET THAT WAS ITS MEMBER IS BEING DELETED. + + // It is ensured that the group object exists here because GroupKeeper + // keeps it busy, even if you just closed the socket, remove it as a member + // or even the group is empty and was explicitly closed. + if (gkeeper.group) + { + // Functions called below will lock m_GroupLock, which in hierarchy + // lies after m_RecvLock. Must unlock m_RecvLock to be able to lock + // m_GroupLock inside the calls. + InvertedLock unrecv(self->m_RecvLock); + // The current "APP reader" needs to simply decide as to whether + // the next CUDTGroup::recv() call should return with no blocking or not. + // When the group is read-ready, it should update its pollers as it sees fit. + + // NOTE: this call will set lock to m_IncludedGroup->m_GroupLock + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: GROUP: checking if %" << info.seqno << " makes group readable"); + gkeeper.group->updateReadState(self->m_SocketID, info.seqno); + + if (shall_update_group) + { + // A group may need to update the parallelly used idle links, + // should it have any. Pass the current socket position in order + // to skip it from the group loop. + // NOTE: SELF LOCKING. + gkeeper.group->updateLatestRcv(self->m_parent); + } + } +#endif CGlobEvent::triggerEvent(); tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } @@ -5640,25 +5680,24 @@ bool srt::CUDT::prepareBuffers(CUDTException *eout) try { m_pSndBuffer = new CSndBuffer(32, m_iMaxSRTPayloadSize); - // after introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice space. - m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2); - #if ENABLE_NEW_BONDING // Keep the per-socket receiver buffer and receiver loss list empty. // Reception will be redirected to the group directly. if (!m_parent->m_GroupOf) { SRT_ASSERT(m_iISN != SRT_SEQNO_NONE); - m_pRcvBuffer = new srt::CRcvBufferNew(m_iISN, m_config.iRcvBufSize, /*m_pRcvQueue->m_pUnitQueue, */ m_config.bMessageAPI); + m_pRcvBuffer = new srt::CRcvBufferNew(m_iISN, m_config.iRcvBufSize, m_config.bMessageAPI); } #else #if ENABLE_NEW_RCVBUFFER SRT_ASSERT(m_iISN != SRT_SEQNO_NONE); - m_pRcvBuffer = new srt::CRcvBufferNew(m_iISN, m_config.iRcvBufSize, /*m_pRcvQueue->m_pUnitQueue, */ m_config.bMessageAPI); + m_pRcvBuffer = new srt::CRcvBufferNew(m_iISN, m_config.iRcvBufSize, m_config.bMessageAPI); #else m_pRcvBuffer = new CRcvBuffer(m_pRcvQueue->m_pUnitQueue, m_config.iRcvBufSize); #endif #endif + // after introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice space. + m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2); m_pRcvLossList = new CRcvLossList(m_config.iFlightFlagSize); } catch (...) @@ -5732,14 +5771,14 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& HandshakeSide hsd = w_hs.v5() || !m_config.bDataSender ? HSD_RESPONDER : HSD_INITIATOR; + // If the resources couldn't be properly created, + // the connection should be rejected. + // + // Respond with the rejection message and exit with exception + // so that the caller will know that this new socket should be deleted. if (!createCrypter(hsd, w_hs.v5())) { HLOGC(cnlog.Debug, log << "acceptAndRespond: createCrypter failed - responding with REJECT."); - // If the SRT Handshake extension was provided and wasn't interpreted - // correctly, the connection should be rejected. - // - // Respond with the rejection message and exit with exception - // so that the caller will know that this new socket should be deleted. w_hs.m_iReqType = URQFailure(m_RejectReason); throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } @@ -5784,11 +5823,6 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& if (!prepareBuffers(NULL)) { HLOGC(cnlog.Debug, log << "acceptAndRespond: prepareBuffers failed - responding with REJECT."); - // If the SRT Handshake extension was provided and wasn't interpreted - // correctly, the connection should be rejected. - // - // Respond with the rejection message and exit with exception - // so that the caller will know that this new socket should be deleted. w_hs.m_iReqType = URQFailure(m_RejectReason); throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } @@ -6916,7 +6950,6 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ if (m_parent->m_GroupOf) throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI); - #endif UniqueLock recvguard (m_RecvLock); @@ -8859,7 +8892,7 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr #if ENABLE_OLD_BONDING if (drift_updated && gk.group) { - m_parent->m_GroupOf->synchronizeDrift(this); + gk.group->synchronizeDrift(this); } #endif } @@ -9762,8 +9795,9 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // This part is being used in case of groups that use the scheduler // for sending packets. The scheduler is used in general for groups that - // pick up selectively the packets that will be sent. Not use for groups - // that simply pick up packets as they come in the scheduling order and priorities. + // pick up selectively the packets that will be sent (mainly balancing). + // Not in use for groups that simply pick up packets as they come in the + // scheduling order and priorities. if (m_parent->m_GroupMemberData && m_parent->m_GroupMemberData->use_send_schedule) { // If this socket is a group member of a group that uses the send scheduler, @@ -9831,7 +9865,7 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // 1. You need to pick up the seq that is extracted from the schedule. // 2. If this sequence is not in the "fresh range", simply skip this // (and report IPE because this should never happen). - // 3. If this sequence is in the "fresh range" than it can be + // 3. If this sequence is in the "fresh range" then it can be // exactly the packet with the required sequence number, or // otherwise this packet should be simply discarded and the // operation should be retried. We know that it won't roll in @@ -9839,7 +9873,7 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // case scenario you'll have several packets to discard until // you reach the end of buffer (which should always retrieve // at least one last packet from the buffer provided that the - // sequence range was first verified. + // sequence range was first verified). SRT_ASSERT( !(m_iSndCurrSeqNo == SRT_SEQNO_NONE || m_iSndNextSeqNo == SRT_SEQNO_NONE) ); if (m_iSndCurrSeqNo == SRT_SEQNO_NONE || m_iSndNextSeqNo == SRT_SEQNO_NONE) @@ -12672,4 +12706,12 @@ void srt::CUDT::processKeepalive(const CPacket& ctrlpkt SRT_ATR_UNUSED, const ti } #endif + // XXX This is likely required, but the call in this place may cause + // a potential deadlock. Try maybe to schedule it somehow. +#if 0 // ENABLE_NEW_RCVBUFFER + ScopedLock lck(m_RcvBufferLock); + m_pRcvBuffer->updateTsbPdTimeBase(ctrlpkt.getMsgTimeStamp()); + if (m_config.bDriftTracer) + m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, -1); +#endif } From 496e6a74338e8ac16fad261aeaa5343d3e16f4f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 22 Sep 2022 17:25:14 +0200 Subject: [PATCH 16/62] Exported fragment to a separate function --- srtcore/core.cpp | 517 +++++++++++++++++++++++++++-------------------- srtcore/core.h | 6 + 2 files changed, 303 insertions(+), 220 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 93224f418..1972d57c5 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9829,20 +9829,10 @@ bool srt::CUDT::overrideSndSeqNo(int32_t seq) return true; } -int srt::CUDT::processData(CUnit* in_unit) +int srt::CUDT::checkLazySpawnLatencyThread() { - if (m_bClosing) - return -1; - - CPacket &packet = in_unit->m_Packet; - - // Just heard from the peer, reset the expiration count. - m_iEXPCount = 1; - m_tsLastRspTime.store(steady_clock::now()); - const bool need_tsbpd = m_bTsbPd || m_bGroupTsbPd; - // We are receiving data, start tsbpd thread if TsbPd is enabled if (need_tsbpd && !m_RcvTsbPdThread.joinable()) { ScopedLock lock(m_RcvTsbPdStartupLock); @@ -9869,9 +9859,264 @@ int srt::CUDT::processData(CUnit* in_unit) return -1; } + return 0; +} + +CUDT::time_point srt::CUDT::getPacketPTS(void*, const CPacket& packet) +{ + return m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); +} + +static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; + + +int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, bool& w_reorder_prevent_lossreport, CUDT::loss_seqs_t& w_srt_loss_seqs) +{ + int initial_loss_ttl = 0; + if (m_bPeerRexmitFlag) + initial_loss_ttl = m_iReorderTolerance; + + bool excessive SRT_ATR_UNUSED = true; // stays true unless it was successfully added + w_reorder_prevent_lossreport = false; + + // Loop over all incoming packets that were filtered out. + // In case when there is no filter, there's just one packet in 'incoming', + // the one that came in the input of this function. + for (vector::const_iterator unitIt = incoming.begin(); unitIt != incoming.end(); ++unitIt) + { + CUnit * u = *unitIt; + CPacket &rpkt = u->m_Packet; + const int pktrexmitflag = m_bPeerRexmitFlag ? (rpkt.getRexmitFlag() ? 1 : 0) : 2; + + time_point pts = steady_clock::now() + milliseconds_from(m_iTsbPdDelay_ms); + IF_HEAVY_LOGGING(pts = getPacketPTS(NULL, rpkt)); + + int buffer_add_result; + bool adding_successful = true; + + // m_iRcvLastSkipAck is the base sequence number for the receiver buffer. + // This is the offset in the buffer; if this is negative, it means that + // this sequence is already in the past and the buffer is not interested. + // Meaning, this packet will be rejected, even if it could potentially be + // one of missing packets in the transmission. + int32_t offset = CSeqNo::seqoff(m_iRcvLastSkipAck, rpkt.m_iSeqNo); + + IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); + + if (offset < 0) + { + IF_HEAVY_LOGGING(exc_type = "BELATED"); + enterCS(m_StatsLock); + const double bltime = (double) CountIIR( + uint64_t(m_stats.traceBelatedTime) * 1000, + count_microseconds(steady_clock::now() - pts), 0.2); + + m_stats.traceBelatedTime = bltime / 1000.0; + m_stats.rcvr.recvdBelated.count(rpkt.getLength()); + leaveCS(m_StatsLock); + HLOGC(qrlog.Debug, + log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo << " offset=" << offset << " (BELATED/" + << rexmitstat[pktrexmitflag] << ") FLAGS: " << rpkt.MessageFlagStr()); + continue; + } + + int avail_bufsize = 0; // needed in logging + + // This is executed only when bonding is enabled and only + // with the new buffer (in which case the buffer is in the group). + + avail_bufsize = (int) getAvailRcvBufferSizeNoLock(); + if (offset >= avail_bufsize) + { + // This is already a sequence discrepancy. Probably there could be found + // some way to make it continue reception by overriding the sequence and + // make a kinda TLKPTDROP, but there has been found no reliable way to do this. + if (m_bTsbPd && m_bTLPktDrop && m_pRcvBuffer->empty()) + { + // Only in live mode. In File mode this shall not be possible + // because the sender should stop sending in this situation. + // In Live mode this means that there is a gap between the + // lowest sequence in the empty buffer and the incoming sequence + // that exceeds the buffer size. Receiving data in this situation + // is no longer possible and this is a point of no return. + + LOGC(qrlog.Error, log << CONID() << + "SEQUENCE DISCREPANCY. BREAKING CONNECTION." + " seq=" << rpkt.m_iSeqNo + << " buffer=(" << m_iRcvLastSkipAck + << ":" << m_iRcvCurrSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, int(m_pRcvBuffer->capacity()) - 1) + << "), " << (offset-avail_bufsize+1) + << " past max. Reception no longer possible. REQUESTING TO CLOSE."); + + return -2; + } + else + { +#if ENABLE_NEW_RCVBUFFER + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo + << ", insert offset " << offset << ". " + << m_pRcvBuffer->strFullnessState(m_iRcvLastAck, steady_clock::now()) + ); +#else + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo + << ", insert offset " << offset << ". " + << m_pRcvBuffer->strFullnessState(steady_clock::now()) + ); +#endif + + return -1; + } + } + +#if ENABLE_NEW_RCVBUFFER + buffer_add_result = m_pRcvBuffer->insert(u); +#else + buffer_add_result = m_pRcvBuffer->addData(u, offset); +#endif + if (buffer_add_result < 0) + { + // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. + // So this packet is "redundant". + IF_HEAVY_LOGGING(exc_type = "UNACKED"); + adding_successful = false; + } + else + { + w_new_inserted = true; + + IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); + excessive = false; + if (u->m_Packet.getMsgCryptoFlags() != EK_NOENC) + { + EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt((u->m_Packet)) : ENCS_NOTSUP; + if (rc != ENCS_CLEAR) + { + // Heavy log message because if seen once the message may happen very often. + HLOGC(qrlog.Debug, log << CONID() << "ERROR: packet not decrypted, dropping data."); + adding_successful = false; + IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); + + ScopedLock lg(m_StatsLock); + m_stats.rcvr.undecrypted.count(stats::BytesPackets(rpkt.getLength(), 1)); + } + } + } + + if (adding_successful) + { + ScopedLock statslock(m_StatsLock); + m_stats.rcvr.recvdUnique.count(u->m_Packet.getLength()); + } + +#if ENABLE_HEAVY_LOGGING + std::ostringstream expectspec; + if (excessive) + expectspec << "EXCESSIVE(" << exc_type << ")"; + else + expectspec << "ACCEPTED"; + + std::ostringstream bufinfo; + + if (m_pRcvBuffer) + { + bufinfo << " BUFr=" << avail_bufsize + << " avail=" << getAvailRcvBufferSizeNoLock() + << " buffer=(" << m_iRcvLastSkipAck + << ":" << m_iRcvCurrSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, m_pRcvBuffer->capacity()-1) + << ")"; + } + + // Empty buffer info in case of groupwise receiver. + // There's no way to obtain this information here. + + LOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo + << " offset=" << offset + << bufinfo.str() + << " RSL=" << expectspec.str() + << " SN=" << rexmitstat[pktrexmitflag] + << " FLAGS: " + << rpkt.MessageFlagStr()); +#endif + + // Decryption should have made the crypto flags EK_NOENC. + // Otherwise it's an error. + if (adding_successful) + { + HLOGC(qrlog.Debug, + log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo)); + + // Lame way to make an optional variable. + bool have_loss = false; + + if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. + { + int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); + int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); + + have_loss = true; + w_srt_loss_seqs.push_back(make_pair(seqlo, seqhi)); + HLOGC(qrlog.Debug, log << "pkt/LOSS DETECTED: %" << seqlo << " - %" << seqhi); + } + + if (initial_loss_ttl && have_loss) + { + // pack loss list for (possibly belated) NAK + // The LOSSREPORT will be sent in a while. + + ScopedLock lg(m_RcvLossLock); + for (loss_seqs_t::iterator i = w_srt_loss_seqs.begin(); i != w_srt_loss_seqs.end(); ++i) + { + m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); + HLOGC(qrlog.Debug, + log << "FreshLoss: added sequences: %(" << i->first << "-" << i->second + << ") tolerance: " << initial_loss_ttl); + + } + w_reorder_prevent_lossreport = true; + } + } + + // Update the current largest sequence number that has been received. + // Or it is a retransmitted packet, remove it from receiver loss list. + // + // Group note: for the new group receiver the group hosts the receiver + // buffer, but the socket still maintains the losses. + if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvCurrSeqNo) > 0) + { + m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received + } + else + { + unlose(rpkt); // was BELATED or RETRANSMITTED + w_was_sent_in_order &= 0 != pktrexmitflag; + } + } + + return 0; +} + +int srt::CUDT::processData(CUnit* in_unit) +{ + if (m_bClosing) + return -1; + + CPacket &packet = in_unit->m_Packet; + + // Just heard from the peer, reset the expiration count. + m_iEXPCount = 1; + m_tsLastRspTime.store(steady_clock::now()); + + + // We are receiving data, start tsbpd thread if TsbPd is enabled + if (-1 == checkLazySpawnLatencyThread()) + { + return -1; + } + const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2; #if ENABLE_HEAVY_LOGGING - static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; string rexmit_reason; #endif @@ -10032,223 +10277,55 @@ int srt::CUDT::processData(CUnit* in_unit) } #endif + // NULL time by default + time_point next_tsbpd_avail; + bool new_inserted = false; + + if (m_PacketFilter) + { + // Stuff this data into the filter + m_PacketFilter.receive(in_unit, (incoming), (filter_loss_seqs)); + HLOGC(qrlog.Debug, + log << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs) + << " loss to report, " + << (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF" + : "REPORT ONLY THOSE")); + } + else + { + // Stuff in just one packet that has come in. + incoming.push_back(in_unit); + } + { // Start of offset protected section // Prevent TsbPd thread from modifying Ack position while adding data // offset from RcvLastAck in RcvBuffer must remain valid between seqoff() and addData() UniqueLock recvbuf_acklock(m_RcvBufferLock); - - // vector undec_units; - if (m_PacketFilter) - { - // Stuff this data into the filter - m_PacketFilter.receive(in_unit, (incoming), (filter_loss_seqs)); - HLOGC(qrlog.Debug, - log << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs) - << " loss to report, " - << (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF" - : "REPORT ONLY THOSE")); - } - else - { - // Stuff in just one packet that has come in. - incoming.push_back(in_unit); - } - - bool excessive = true; // stays true unless it was successfully added - // Needed for possibly check for needsQuickACK. bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.m_iSeqNo, m_iRcvLastSkipAck) < 0); - // Loop over all incoming packets that were filtered out. - // In case when there is no filter, there's just one packet in 'incoming', - // the one that came in the input of this function. - for (vector::iterator unitIt = incoming.begin(); unitIt != incoming.end(); ++unitIt) - { - CUnit * u = *unitIt; - CPacket &rpkt = u->m_Packet; - - // m_iRcvLastSkipAck is the base sequence number for the receiver buffer. - // This is the offset in the buffer; if this is negative, it means that - // this sequence is already in the past and the buffer is not interested. - // Meaning, this packet will be rejected, even if it could potentially be - // one of missing packets in the transmission. - int32_t offset = CSeqNo::seqoff(m_iRcvLastSkipAck, rpkt.m_iSeqNo); - - IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); - - if (offset < 0) - { - IF_HEAVY_LOGGING(exc_type = "BELATED"); - steady_clock::time_point tsbpdtime = m_pRcvBuffer->getPktTsbPdTime(rpkt.getMsgTimeStamp()); - const double bltime = (double) CountIIR( - uint64_t(m_stats.traceBelatedTime) * 1000, - count_microseconds(steady_clock::now() - tsbpdtime), 0.2); - - enterCS(m_StatsLock); - m_stats.traceBelatedTime = bltime / 1000.0; - m_stats.rcvr.recvdBelated.count(rpkt.getLength()); - leaveCS(m_StatsLock); - HLOGC(qrlog.Debug, - log << CONID() << "RECEIVED: seq=" << packet.m_iSeqNo << " offset=" << offset << " (BELATED/" - << rexmitstat[pktrexmitflag] << rexmit_reason << ") FLAGS: " << packet.MessageFlagStr()); - continue; - } - - const int avail_bufsize = (int) getAvailRcvBufferSizeNoLock(); - if (offset >= avail_bufsize) - { - // This is already a sequence discrepancy. Probably there could be found - // some way to make it continue reception by overriding the sequence and - // make a kinda TLKPTDROP, but there has been found no reliable way to do this. - if (m_bTsbPd && m_bTLPktDrop && m_pRcvBuffer->empty()) - { - // Only in live mode. In File mode this shall not be possible - // because the sender should stop sending in this situation. - // In Live mode this means that there is a gap between the - // lowest sequence in the empty buffer and the incoming sequence - // that exceeds the buffer size. Receiving data in this situation - // is no longer possible and this is a point of no return. - - LOGC(qrlog.Error, log << CONID() << - "SEQUENCE DISCREPANCY. BREAKING CONNECTION." - " seq=" << rpkt.m_iSeqNo - << " buffer=(" << m_iRcvLastSkipAck - << ":" << m_iRcvCurrSeqNo // -1 = size to last index - << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, int(m_pRcvBuffer->capacity()) - 1) - << "), " << (offset-avail_bufsize+1) - << " past max. Reception no longer possible. REQUESTING TO CLOSE."); - - // This is a scoped lock with AckLock, but for the moment - // when processClose() is called this lock must be taken out, - // otherwise this will cause a deadlock. We don't need this - // lock anymore, and at 'return' it will be unlocked anyway. - recvbuf_acklock.unlock(); - processClose(); - return -1; - } - else - { -#if ENABLE_NEW_RCVBUFFER - LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo - << ", insert offset " << offset << ". " - << m_pRcvBuffer->strFullnessState(m_iRcvLastAck, steady_clock::now()) - ); -#else - LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo - << ", insert offset " << offset << ". " - << m_pRcvBuffer->strFullnessState(steady_clock::now()) - ); -#endif - - return -1; - } - } + int res = handleSocketPacketReception(incoming, + (new_inserted), + (was_sent_in_order), + (reorder_prevent_lossreport), + (srt_loss_seqs)); - bool adding_successful = true; -#if ENABLE_NEW_RCVBUFFER - if (m_pRcvBuffer->insert(u) < 0) -#else - if (m_pRcvBuffer->addData(u, offset) < 0) -#endif - { - // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. - // So this packet is "redundant". - IF_HEAVY_LOGGING(exc_type = "UNACKED"); - adding_successful = false; - } - else - { - IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); - excessive = false; - if (u->m_Packet.getMsgCryptoFlags() != EK_NOENC) - { - EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt((u->m_Packet)) : ENCS_NOTSUP; - if (rc != ENCS_CLEAR) - { - // Heavy log message because if seen once the message may happen very often. - HLOGC(qrlog.Debug, log << CONID() << "ERROR: packet not decrypted, dropping data."); - adding_successful = false; - IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); - - ScopedLock lg(m_StatsLock); - m_stats.rcvr.undecrypted.count(stats::BytesPackets(pktsz, 1)); - } - } - } - - if (adding_successful) - { - ScopedLock statslock(m_StatsLock); - m_stats.rcvr.recvdUnique.count(u->m_Packet.getLength()); - } - -#if ENABLE_HEAVY_LOGGING - std::ostringstream expectspec; - if (excessive) - expectspec << "EXCESSIVE(" << exc_type << rexmit_reason << ")"; - else - expectspec << "ACCEPTED"; - - LOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo - << " offset=" << offset - << " BUFr=" << avail_bufsize - << " avail=" << getAvailRcvBufferSizeNoLock() - << " buffer=(" << m_iRcvLastSkipAck - << ":" << m_iRcvCurrSeqNo // -1 = size to last index - << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, m_pRcvBuffer->capacity()-1) - << ") " - << " RSL=" << expectspec.str() - << " SN=" << rexmitstat[pktrexmitflag] - << " FLAGS: " - << rpkt.MessageFlagStr()); -#endif - - // Decryption should have made the crypto flags EK_NOENC. - // Otherwise it's an error. - if (adding_successful) - { - // XXX move this code do CUDT::defaultPacketArrival and call it from here: - // srt_loss_seqs = CALLBACK_CALL(m_cbPacketArrival, rpkt); - - HLOGC(qrlog.Debug, - log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo)); - - if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. - { - int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); - int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); - - srt_loss_seqs.push_back(make_pair(seqlo, seqhi)); - - if (initial_loss_ttl) - { - // pack loss list for (possibly belated) NAK - // The LOSSREPORT will be sent in a while. + if (res == -2) + { + // This is a scoped lock with AckLock, but for the moment + // when processClose() is called this lock must be taken out, + // otherwise this will cause a deadlock. We don't need this + // lock anymore, and at 'return' it will be unlocked anyway. + recvbuf_acklock.unlock(); + processClose(); - for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i) - { - m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); - } - HLOGC(qrlog.Debug, - log << "FreshLoss: added sequences: " << Printable(srt_loss_seqs) - << " tolerance: " << initial_loss_ttl); - reorder_prevent_lossreport = true; - } - } - } + return -1; + } - // Update the current largest sequence number that has been received. - // Or it is a retransmitted packet, remove it from receiver loss list. - if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvCurrSeqNo) > 0) - { - m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received - } - else - { - unlose(rpkt); // was BELATED or RETRANSMITTED - was_sent_in_order &= 0 != pktrexmitflag; - } + if (res == -1) + { + return -1; } // This is moved earlier after introducing filter because it shouldn't @@ -10269,11 +10346,11 @@ int srt::CUDT::processData(CUnit* in_unit) } } - if (excessive) + if (!new_inserted) { return -1; } - } // End of recvbuf_acklock + } if (m_bClosing) { diff --git a/srtcore/core.h b/srtcore/core.h index fa58ca7c2..72946a5bf 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1062,6 +1062,12 @@ class CUDT std::pair packData(CPacket& packet); int processData(CUnit* unit); + int handleSocketPacketReception(const std::vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, bool& w_reorder_prevent_loss, CUDT::loss_seqs_t& w_srt_loss_seqs); + + // Group passed here by void* because in the current imp it's + // unused and shall not be used in case when bonding is off + time_point getPacketPTS(void* grp, const CPacket& packet); + int checkLazySpawnLatencyThread(); void processClose(); /// Process the request after receiving the handshake from caller. From dbc37295236bc7c8c604e67234278fdc50859c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 23 Sep 2022 17:43:56 +0200 Subject: [PATCH 17/62] Fixed one deadlock. Added verification in snd-loss-removal procedure --- srtcore/core.cpp | 2 +- srtcore/group.cpp | 5 ++--- srtcore/group.h | 2 +- srtcore/list.cpp | 15 ++++++++++++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 8fdddf212..3a6a81cbe 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -6754,7 +6754,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) // schedule, for all others it does nothing. if (m_parent->m_GroupMemberData->use_send_schedule) { - if (!m_parent->m_GroupOf->updateSendPacketUnique(seqno)) + if (!m_parent->m_GroupOf->updateSendPacketUnique_LOCKED(seqno)) { throw CUDTException(MJ_CONNECTION, MN_CONNLOST); } diff --git a/srtcore/group.cpp b/srtcore/group.cpp index c7b048175..78651fe99 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -6126,10 +6126,9 @@ CUDTGroup::gli_t CUDTGroup::linkSelect_window(const CUDTGroup::BalancingLinkStat } // Update on adding a new fresh packet to the sender buffer. -bool CUDTGroup::updateSendPacketUnique(int32_t single_seq) +// [[using locked(m_GroupLock)]] +bool CUDTGroup::updateSendPacketUnique_LOCKED(int32_t single_seq) { - ScopedLock guard(m_GroupLock); - // Check first if the packet wasn't already scheduled // If so, do nothing and return success. for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d) diff --git a/srtcore/group.h b/srtcore/group.h index 638dcf9f2..d70f74b25 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -965,7 +965,7 @@ class CUDTGroup void updateLatestRcv(srt::CUDTSocket*); #if ENABLE_NEW_RCVBUFFER - SRT_ATR_NODISCARD bool updateSendPacketUnique(int32_t single_seq); + SRT_ATR_NODISCARD bool updateSendPacketUnique_LOCKED(int32_t single_seq); SRT_ATR_NODISCARD bool updateSendPacketLoss(bool use_send_sched, const std::vector< std::pair >& seqlist); SRT_ATR_NODISCARD bool getSendSchedule(SocketData* d, std::vector& w_sched); diff --git a/srtcore/list.cpp b/srtcore/list.cpp index c47065cfe..3d4b375fa 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -415,10 +415,13 @@ int srt::CSndLossList::rangecmp(int32_t seq, int32_t seqlo, int32_t seqhi) SRT_ASSERT(seqlo != SRT_SEQNO_NONE); int cmp = CSeqNo::seqcmp(seq, seqlo); - if (seqhi == SRT_SEQNO_NONE || cmp < 0) + + // If seqhi == NONE, we only compare against seqlo, so return this value already. + // If seq <=% seqlo, we already know the result as well. + if (seqhi == SRT_SEQNO_NONE || cmp <= 0) return cmp; - // Since now seq may be only >= seqlo, check seqhi + // Since now may be only seq %> seqlo, check seqhi cmp = CSeqNo::seqcmp(seq, seqhi); if (cmp > 0) return cmp; @@ -447,9 +450,15 @@ bool srt::CSndLossList::popLostSeq(int32_t seq) int loc = m_iHead; int* prev_next = &m_iHead; - for (;;) + for (int i = 0; i < m_iLength; ++i) { Seq& cell = m_caSeq[loc]; + SRT_ASSERT(cell.seqstart != SRT_SEQNO_NONE); + + // Ensure that if we've reached the last element by length + // then the next pointer is -1, and vice versa. + SRT_ASSERT((i == m_iLength - 1) ^ (cell.inext != -1)); + int cmp = rangecmp(seq, cell.seqstart, cell.seqend); if (cmp < 0) { From b51410866dfe274cb1b6b73bdf74d718cd8652af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 26 Sep 2022 10:44:04 +0200 Subject: [PATCH 18/62] Added handling of balancing group for srt-test-live. Fixed permissions. --- testing/srt-test-relay.cpp | 0 testing/testmedia.cpp | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) mode change 100755 => 100644 testing/srt-test-relay.cpp mode change 100755 => 100644 testing/testmedia.cpp diff --git a/testing/srt-test-relay.cpp b/testing/srt-test-relay.cpp old mode 100755 new mode 100644 diff --git a/testing/testmedia.cpp b/testing/testmedia.cpp old mode 100755 new mode 100644 index 5193cf5fa..5435fec08 --- a/testing/testmedia.cpp +++ b/testing/testmedia.cpp @@ -944,16 +944,37 @@ void TransmitGroupSocketConnect(void* srtcommon, SRTSOCKET sock, int error, cons Verb() << " IPE: LINK NOT FOUND???]"; } -void SrtCommon::OpenGroupClient() +SRT_GROUP_TYPE ResolveGroupType(const string& name) { - SRT_GROUP_TYPE type = SRT_GTYPE_UNDEFINED; + static struct + { + string name; + SRT_GROUP_TYPE type; + } table [] { +#define E(n) {#n, SRT_GTYPE_##n} + E(BROADCAST), + E(BACKUP), + E(BALANCING) - // Resolve group type. - if (m_group_type == "broadcast") - type = SRT_GTYPE_BROADCAST; - else if (m_group_type == "backup") - type = SRT_GTYPE_BACKUP; - else +#undef E + }; + + typedef int charxform(int c); + + string uname; + transform(name.begin(), name.end(), back_inserter(uname), (charxform*)(&toupper)); + + for (auto& x: table) + if (x.name == uname) + return x.type; + + return SRT_GTYPE_UNDEFINED; +} + +void SrtCommon::OpenGroupClient() +{ + SRT_GROUP_TYPE type = ResolveGroupType(m_group_type); + if (type == SRT_GTYPE_UNDEFINED) { Error("With //group, type='" + m_group_type + "' undefined"); } From 03214ff8bf36706e0378335af8288211fcd92fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 30 Sep 2022 18:03:29 +0200 Subject: [PATCH 19/62] Applied changes from PR 2467-before merging --- srtcore/core.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 1972d57c5..4c50d30cf 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9940,14 +9940,12 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& // that exceeds the buffer size. Receiving data in this situation // is no longer possible and this is a point of no return. - LOGC(qrlog.Error, log << CONID() << - "SEQUENCE DISCREPANCY. BREAKING CONNECTION." - " seq=" << rpkt.m_iSeqNo - << " buffer=(" << m_iRcvLastSkipAck - << ":" << m_iRcvCurrSeqNo // -1 = size to last index - << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, int(m_pRcvBuffer->capacity()) - 1) - << "), " << (offset-avail_bufsize+1) - << " past max. Reception no longer possible. REQUESTING TO CLOSE."); + LOGC(qrlog.Error, + log << CONID() << "SEQUENCE DISCREPANCY. BREAKING CONNECTION. seq=" << rpkt.m_iSeqNo + << " buffer=(" << m_iRcvLastAck << ":" << m_iRcvCurrSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(m_iRcvLastAck, int(m_pRcvBuffer->capacity()) - 1) << "), " + << (offset - avail_bufsize + 1) + << " past max. Reception no longer possible. REQUESTING TO CLOSE."); return -2; } @@ -10020,8 +10018,8 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& if (m_pRcvBuffer) { - bufinfo << " BUFr=" << avail_bufsize - << " avail=" << getAvailRcvBufferSizeNoLock() + bufinfo + << " avail=" << avail_bufsize << " buffer=(" << m_iRcvLastSkipAck << ":" << m_iRcvCurrSeqNo // -1 = size to last index << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, m_pRcvBuffer->capacity()-1) From a1bb54077d548ee826dfac7e8b7387491af731a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Wed, 5 Oct 2022 12:04:50 +0200 Subject: [PATCH 20/62] Fixed balancing mode sending. Fixed loss list selective extraction. Added some more logs --- srtcore/buffer.cpp | 7 +- srtcore/core.cpp | 124 ++++++++++++++++++++++------- srtcore/core.h | 11 ++- srtcore/group.cpp | 98 ++++++++++++++++------- srtcore/group.h | 33 +++++++- srtcore/group_common.cpp | 7 ++ srtcore/group_common.h | 2 + srtcore/list.cpp | 148 ++++++++++++++++++++++++++-------- srtcore/list.h | 27 ++++++- test/test_list.cpp | 157 ++++++++++++++++++++++++++++++++++--- testing/srt-test-relay.cpp | 0 testing/testmedia.cpp | 0 12 files changed, 507 insertions(+), 107 deletions(-) mode change 100644 => 100755 testing/srt-test-relay.cpp mode change 100644 => 100755 testing/testmedia.cpp diff --git a/srtcore/buffer.cpp b/srtcore/buffer.cpp index 20149446f..a98821eb4 100644 --- a/srtcore/buffer.cpp +++ b/srtcore/buffer.cpp @@ -426,7 +426,7 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, // 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, @@ -456,7 +456,10 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, continue; } - HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: extracting packet size=" << readlen << " to send"); + HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: picked up packet to send: size=" << readlen + << " #" << w_packet.getMsgSeq() + << " %" << w_packet.m_iSeqNo + << " !" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); break; } diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 3a6a81cbe..335337fed 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -6041,7 +6041,7 @@ void srt::CUDT::considerLegacySrtHandshake(const steady_clock::time_point &timeb if (m_iSndHsRetryCnt <= 0) { - HLOGC(cnlog.Debug, log << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt); + //HLOGC(cnlog.Debug, log << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt); return; } @@ -6754,6 +6754,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) // schedule, for all others it does nothing. if (m_parent->m_GroupMemberData->use_send_schedule) { + HLOGC(aslog.Debug, log << CONID() << "sendmsg2: add to group schedule: %" << seqno << " !" << BufferStamp(data, size)); if (!m_parent->m_GroupOf->updateSendPacketUnique_LOCKED(seqno)) { throw CUDTException(MJ_CONNECTION, MN_CONNLOST); @@ -6774,7 +6775,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) HLOGC(aslog.Debug, log << CONID() << "buf:SENDING (BEFORE) srctime:" << (w_mctrl.srctime ? FormatTime(ts_srctime) : "none") << " DATA SIZE: " << size << " sched-SEQUENCE: " << seqno - << " STAMP: " << BufferStamp(data, size)); + << " !" << BufferStamp(data, size)); if (w_mctrl.srctime && w_mctrl.srctime < count_microseconds(m_stats.tsStartTime.time_since_epoch())) { @@ -8413,8 +8414,10 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) return nbsent; } -void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) +void srt::CUDT::updateStateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seqno) { + w_last_sent_seqno = m_iSndCurrSeqNo; + #if ENABLE_BONDING CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); // This is for the call of CSndBuffer::getMsgNoAt that returns @@ -8446,7 +8449,12 @@ void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) // one should happen in case of possible breakup detection, so this // way it doesn't make any sense to handle losses any other way than // per socket, as usual. - gkeeper.group->updateSndLossListOnACK(ackdata_seqno); + bool valid = gkeeper.group->updateOnACK(ackdata_seqno, (w_last_sent_seqno)); + if (!valid) + { + HLOGC(inlog.Debug, log << "updateStateOnACK: " << CONID() << "sent outdated ack %" << ackdata_seqno + << " against last sent %" << w_last_sent_seqno << " ??? "); + } // m_RecvAckLock protects sender's loss list and epoll ScopedLock ack_lock(m_RecvAckLock); @@ -8569,7 +8577,13 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ log << CONID() << "ACK covers: " << m_iSndLastDataAck << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck << "]" << (isLiteAck ? "[LITE]" : "[FULL]")); - updateSndLossListOnACK(ackdata_seqno); + // last_sent_seqno is the value of m_iSndCurrSeqNo in general, + // but for parallel-link groups (broadcast and balancing) this should + // use the value that is remembered in the group and represents the + // latest sequence sent for the group, no matter through which link + // it was sent. + int32_t last_sent_seqno; + updateStateOnACK(ackdata_seqno, (last_sent_seqno)); // Process a lite ACK if (isLiteAck) @@ -8615,13 +8629,13 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ enterCS(m_RecvAckLock); // Check the validation of the ack - if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0) + if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(last_sent_seqno)) > 0) { leaveCS(m_RecvAckLock); // this should not happen: attack or bug LOGC(gglog.Error, log << CONID() << "ATTACK/IPE: incoming ack seq " << ackdata_seqno << " exceeds current " - << m_iSndCurrSeqNo << " by " << (CSeqNo::seqoff(m_iSndCurrSeqNo, ackdata_seqno) - 1) << "!"); + << last_sent_seqno << " by " << (CSeqNo::seqoff(last_sent_seqno, ackdata_seqno) - 1) << "! - BREAKING"); m_bBroken = true; m_iBrokenCounter = 0; return; @@ -9058,6 +9072,7 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { groups::SocketData* d = m_parent->m_GroupMemberData; + HLOGC(aslog.Debug, log << CONID() << "processCtrl(loss): adding to group loss sched&list: %" << Printable(losses)); // This will: // 1. Add the loss to the group loss list // 2. If use_send_schedule, it will also schedule these packets. @@ -9093,7 +9108,7 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { LOGC(inlog.Warn, log << CONID() << "out-of-band LOSSREPORT received; BUG or ATTACK - last sent %" << m_iSndCurrSeqNo - << " vs loss %" << wrong_loss); + << " vs loss %" << wrong_loss << " - BREAKING"); // this should not happen: attack or bug m_bBroken = true; m_iBrokenCounter = 0; @@ -9815,6 +9830,19 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime return false; } +#if ENABLE_HEAVY_LOGGING + { + HLOGC(qslog.Debug, log << CONID() << "packData(sched): current schedule (" << seqs.size() << "):"); + ostringstream so; + for (size_t i = 0; i < seqs.size(); ++i) + { + HLOGC(qslog.Debug, log << "... [" << i << "] %" << seqs[i].seq + << " [" << groups::SeqTypeStr(seqs[i].type) << "]"); + } + + } +#endif + int nremoved = 0; for (size_t i = 0; i < seqs.size(); ++i) { @@ -9825,11 +9853,15 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime nremoved = i+1; + HLOGC(qslog.Debug, log << "packData(sched): got %" << ss.seq + << " [" << groups::SeqTypeStr(ss.type) << "] [" << i << "]"); + // XXX see below //int msglen; if (ss.type == groups::SQT_PFILTER) { + HLOGC(qslog.Debug, log << "... filter type, NOT IMPLEMENTED"); // XXX packet filter extraction currently not implemented, do not use. continue; //filter_ctl_pkt = true; @@ -9856,6 +9888,8 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime << ":loss in the schedule is missing from the loss list"); continue; } + HLOGC(qslog.Debug, log << "... ok, will send the REXMIT %" << ss.seq); + break; // exit the schedule loop and execute the event request } else if (ss.type == groups::SQT_FRESH) { @@ -9890,6 +9924,8 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime continue; } + HLOGC(qslog.Debug, log << "... ok, will send the unique %" << ss.seq); + // After this condition we know that the situation is: // The given sequence is one of the o below, // and the very first call to packUniqueData() will pick @@ -9902,18 +9938,38 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // ^ ^ // m_iSndCurrSeqNo m_iSndNextSeqNo - if (!packUniqueData((w_packet), (origintime))) + // Roll here until you finally extract the packet with the expected + // sequence. + for (;;) { - // Kinda impossible, but still handle it. - payload = 0; - break; - } + if (!packUniqueData((w_packet), (origintime))) + { + // Kinda impossible, but still handle it. + LOGC(qslog.Debug, log << "packetData(sched): IPE: nothing extracted from the buffer tho sched requested %" << ss.seq); + payload = 0; // force uplevel break + break; + } + + payload = (int) w_packet.getLength(); + + if (w_packet.m_iSeqNo == ss.seq) + { + break; + } + + HLOGC(qslog.Debug, log << "packData(sched): extracted %" << w_packet.m_iSeqNo << " while expecting scheduled %" << ss.seq << " - retrying."); - if (w_packet.m_iSeqNo != ss.seq) - { // This is not the packet we are looking for. - continue; + // Fallback, just in case. + if (CSeqNo::seqcmp(w_packet.m_iSeqNo, ss.seq) > 0) + { + LOGC(qslog.Debug, log << "packetData(sched): IPE: extraction from the sender buffer found %" << w_packet.m_iSeqNo << " past %" << ss.seq); + payload = 0; // force uplevel break + break; + } } + if (payload == 0) + break; new_packet_packed = true; @@ -9921,8 +9977,10 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime if ((w_packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0) probe = true; - payload = (int) w_packet.getLength(); reason = "normal"; + + // The packet was successfully picked up, stop the loop here. + break; } else // SQT_SKIP { @@ -9946,10 +10004,13 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // by removing elements - only this function can remove elements from there, or // when closing a socket. So this will always cover these sequences that have been // extracted here above. + + HLOGC(qslog.Debug, log << "packData(sched): discard " << nremoved << "/" << seqs.size() << " scheduled events"); m_parent->m_GroupOf->discardSendSchedule(m_parent->m_GroupMemberData, nremoved); if (!payload) { + HLOGC(qslog.Debug, log << "packData(sched): no packet extracted from the buffer - exitting"); // XXX consider making that common for payload = 0 m_tsNextSendTime = steady_clock::time_point(); m_tdSendTimeDiff = steady_clock::duration::zero(); @@ -10059,7 +10120,7 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime #if ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr() HLOGC(qslog.Debug, - log << CONID() << "packData: " << reason << " packet seq=" << w_packet.m_iSeqNo << " (ACK=" << m_iSndLastAck + log << CONID() << "packData: " << reason << " packet %" << w_packet.m_iSeqNo << " (ACK=" << m_iSndLastAck << " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << w_packet.MessageFlagStr() << ")"); #endif @@ -10131,7 +10192,7 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet, time_point& w_origintime) const int flightspan = getFlightSpan(); if (cwnd <= flightspan) { - HLOGC(qslog.Debug, log << "packData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_dCongestionWindow + HLOGC(qslog.Debug, log << "packUniqueData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_dCongestionWindow << ")=" << cwnd << " seqlen=(" << m_iSndLastAck << "-" << m_iSndCurrSeqNo << ")=" << flightspan); return false; } @@ -10148,10 +10209,13 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet, time_point& w_origintime) { // Some packets were skipped due to TTL expiry. m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo, pktskipseqno); + HLOGC(qslog.Debug, log << "packUniqueData: reading skipped " << pktskipseqno << " seq up to %" << m_iSndCurrSeqNo + << " due to TTL expiry"); } if (pld_size == 0) { + HLOGC(qslog.Debug, log << "packUniqueData: nothing extracted from the buffer"); return false; } @@ -10161,8 +10225,8 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet, time_point& w_origintime) m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo); #if ENABLE_BONDING - // Fortunately the group itself isn't being accessed. - if (m_parent->m_GroupOf) + CUDTUnited::GroupKeeper gk(uglobal(), m_parent); + if (gk.group) { const int packetspan = CSeqNo::seqoff(m_iSndCurrSeqNo, w_packet.m_iSeqNo); if (packetspan > 0) @@ -10177,7 +10241,7 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet, time_point& w_origintime) // no ACK to be awaited. We can screw up all the variables that are // initialized from ISN just after connection. LOGC(qslog.Note, - log << CONID() << "packData: Fixing EXTRACTION sequence " << m_iSndCurrSeqNo + log << CONID() << "packUniqueData: Fixing EXTRACTION sequence " << m_iSndCurrSeqNo << " from SCHEDULING sequence " << w_packet.m_iSeqNo << " for the first packet: DIFF=" << packetspan << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); } @@ -10185,7 +10249,7 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet, time_point& w_origintime) { // There will be a serious data discrepancy between the agent and the peer. LOGC(qslog.Error, - log << CONID() << "IPE: packData: Fixing EXTRACTION sequence " << m_iSndCurrSeqNo + log << CONID() << "IPE: packUniqueData: Fixing EXTRACTION sequence " << m_iSndCurrSeqNo << " from SCHEDULING sequence " << w_packet.m_iSeqNo << " in the middle of transition: DIFF=" << packetspan << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); } @@ -10198,7 +10262,7 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet, time_point& w_origintime) seqpair[1] = CSeqNo::decseq(w_packet.m_iSeqNo); const int32_t no_msgno = 0; LOGC(qslog.Debug, - log << CONID() << "packData: Sending DROPREQ: SEQ: " << seqpair[0] << " - " << seqpair[1] << " (" + log << CONID() << "packUniqueData: Sending DROPREQ: SEQ: " << seqpair[0] << " - " << seqpair[1] << " (" << packetspan << " packets)"); sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair)); // In case when this message is lost, the peer will still get the @@ -10216,18 +10280,23 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet, time_point& w_origintime) else if (packetspan < 0) { LOGC(qslog.Error, - log << CONID() << "IPE: packData: SCHEDULING sequence " << w_packet.m_iSeqNo + log << CONID() << "IPE: packUniqueData: SCHEDULING sequence " << w_packet.m_iSeqNo << " is behind of EXTRACTION sequence " << m_iSndCurrSeqNo << ", dropping this packet: DIFF=" << packetspan << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); // XXX: Probably also change the socket state to broken? return false; } +#if ENABLE_NEW_BONDING + int32_t upd SRT_ATR_UNUSED = gk.group->updateSentSeq(m_iSndCurrSeqNo); + HLOGC(qslog.Debug, log << CONID() << "packUniqueData: last sent seq for socket: %" << m_iSndCurrSeqNo + << " group: %" << upd); +#endif } else #endif { HLOGC(qslog.Debug, - log << CONID() << "packData: Applying EXTRACTION sequence " << m_iSndCurrSeqNo + log << CONID() << "packUniqueData: Applying EXTRACTION sequence " << m_iSndCurrSeqNo << " over SCHEDULING sequence " << w_packet.m_iSeqNo << " for socket not in group:" << " DIFF=" << CSeqNo::seqcmp(m_iSndCurrSeqNo, w_packet.m_iSeqNo) << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); @@ -12236,7 +12305,7 @@ bool srt::CUDT::checkExpTimer(const steady_clock::time_point& currtime, int chec // Application will detect this when it calls any UDT methods next time. // HLOGC(xtlog.Debug, - log << "CONNECTION EXPIRED after " << count_milliseconds(currtime - last_rsp_time) << "ms"); + log << "CONNECTION EXPIRED after " << FormatDuration(currtime - last_rsp_time) << " - BREAKING"); m_bClosing = true; m_bBroken = true; m_iBrokenCounter = 30; @@ -12456,6 +12525,7 @@ void srt::CUDT::completeBrokenConnectionDependencies(int errorcode) { // XXX This somehow can cause a deadlock // uglobal()->close(m_parent); + LOGC(smlog.Debug, log << "updateBrokenConnection...: BROKEN SOCKET @" << m_SocketID << " - CLOSING, to be removed from group."); m_parent->setBrokenClosed(); } #endif diff --git a/srtcore/core.h b/srtcore/core.h index f48d4658a..da74bd0ce 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1041,9 +1041,16 @@ class CUDT /// @param ctrlpkt incoming user defined packet void processCtrlUserDefined(const CPacket& ctrlpkt); - /// @brief Update sender's loss list on an incoming acknowledgement. + /// @brief Update sender side socket data according to incoming ACK message. + /// + /// Incoming ACK message marks a point behind which everything is considered + /// received correctly, or at least there's no need to worry about it. This + /// requires to forget anything that refers to packets prior to this number. + /// In case of a group member, this number reflects this state also for the + /// whole group. + /// /// @param ackdata_seqno sequence number of a data packet being acknowledged - void updateSndLossListOnACK(int32_t ackdata_seqno); + void updateStateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seqno); /// Pack a packet from a list of lost packets. /// diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 78651fe99..3d877b4b4 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -968,7 +968,12 @@ void CUDTGroup::syncWithFirstSocket(const CUDT& core, const HandshakeSide side) differ in sender and receiver. */ - m_RcvLastSeqNo = CSeqNo::decseq(core.ISN()); + int32_t butlast_seqno = CSeqNo::decseq(core.ISN()); + m_RcvLastSeqNo = butlast_seqno; + + // This should be the sequence of the latest packet in flight, + // after being send over whichever member connection. + m_SndLastSeqNo = butlast_seqno; m_SndLastDataAck = core.ISN(); if (core.m_bGroupTsbPd) @@ -1536,7 +1541,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool if (!pu || pu->m_bBroken) { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: socket @" << d->id << " detected +Broken - transit to BROKEN"); + log << "grp/sendSelectable: socket @" << d->id << " detected +Broken - transit to BROKEN"); d->sndstate = SRT_GST_BROKEN; d->rcvstate = SRT_GST_BROKEN; } @@ -1546,7 +1551,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool if (d->sndstate == SRT_GST_BROKEN) { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: socket in BROKEN state: @" << d->id + log << "grp/sendSelectable: socket in BROKEN state: @" << d->id << ", sockstatus=" << SockStatusStr(d->ps ? d->ps->getStatus() : SRTS_NONEXIST)); wipeme.push_back(d->id); continue; @@ -1575,7 +1580,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool continue; } - HLOGC(gslog.Debug, log << "grp/sendBroadcast: socket in IDLE state: @" << d->id << " - will activate it"); + HLOGC(gslog.Debug, log << "grp/sendSelectable: socket in IDLE state: @" << d->id << " - will activate it"); // This is idle, we'll take care of them next time // Might be that: // - this socket is idle, while some NEXT socket is running @@ -1589,13 +1594,13 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool if (d->sndstate == SRT_GST_RUNNING) { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: socket in RUNNING state: @" << d->id << " - will send a payload"); + log << "grp/sendSelectable: socket in RUNNING state: @" << d->id << " - will send a payload"); activeLinks.push_back(d); continue; } HLOGC(gslog.Debug, - log << "grp/sendBroadcast: socket @" << d->id << " not ready, state: " << StateStr(d->sndstate) << "(" + log << "grp/sendSelectable: socket @" << d->id << " not ready, state: " << StateStr(d->sndstate) << "(" << int(d->sndstate) << ") - NOT sending, SET AS PENDING"); pendingSockets.push_back(d->id); @@ -1677,7 +1682,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool // Now we can go to the idle links and attempt to send the payload // also over them. - // TODO: { sendBroadcast_ActivateIdleLinks + // TODO: { sendSelectable_ActivateIdleLinks for (vector::iterator i = idleLinks.begin(); i != idleLinks.end(); ++i) { gli_t d = *i; @@ -1689,7 +1694,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool if (curseq != SRT_SEQNO_NONE && curseq != lastseq) { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: socket @" << d->id << ": override snd sequence %" << lastseq << " with %" + log << "grp/sendSelectable: socket @" << d->id << ": override snd sequence %" << lastseq << " with %" << curseq << " (diff by " << CSeqNo::seqcmp(curseq, lastseq) << "); SENDING PAYLOAD: " << BufferStamp(buf, len)); d->ps->core().overrideSndSeqNo(curseq); @@ -1697,7 +1702,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool else { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: socket @" << d->id << ": sequence remains with original value: %" + log << "grp/sendSelectable: socket @" << d->id << ": sequence remains with original value: %" << lastseq << "; SENDING PAYLOAD " << BufferStamp(buf, len)); } @@ -1737,7 +1742,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool if (nextseq != SRT_SEQNO_NONE) { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: $" << id() << ": updating current scheduling sequence %" << nextseq); + log << "grp/sendSelectable: $" << id() << ": updating current scheduling sequence %" << nextseq); m_iLastSchedSeqNo = nextseq; } @@ -1747,7 +1752,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool if (!pendingSockets.empty()) { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: found pending sockets, polling them."); + HLOGC(gslog.Debug, log << "grp/sendSelectable: found pending sockets, polling them."); // These sockets if they are in pending state, they should be added to m_SndEID // at the connecting stage. @@ -1757,7 +1762,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool { // Sanity check - weird pending reported. LOGC(gslog.Error, - log << "grp/sendBroadcast: IPE: reported pending sockets, but EID is empty - wiping pending!"); + log << "grp/sendSelectable: IPE: reported pending sockets, but EID is empty - wiping pending!"); copy(pendingSockets.begin(), pendingSockets.end(), back_inserter(wipeme)); } else @@ -1777,7 +1782,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } - HLOGC(gslog.Debug, log << "grp/sendBroadcast: RDY: " << DisplayEpollResults(sready)); + HLOGC(gslog.Debug, log << "grp/sendSelectable: RDY: " << DisplayEpollResults(sready)); // sockets in EX: should be moved to wipeme. for (vector::iterator i = pendingSockets.begin(); i != pendingSockets.end(); ++i) @@ -1785,7 +1790,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool if (CEPoll::isready(sready, *i, SRT_EPOLL_ERR)) { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: Socket @" << (*i) << " reported FAILURE - moved to wiped."); + log << "grp/sendSelectable: Socket @" << (*i) << " reported FAILURE - moved to wiped."); // Failed socket. Move d to wipeme. Remove from eid. wipeme.push_back(*i); int no_events = 0; @@ -1815,7 +1820,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool // } - // { sendBroadcast_CheckBlockedLinks() + // { sendSelectable_CheckBlockedLinks() // Alright, we've made an attempt to send a packet over every link. // Every operation was done through a non-blocking attempt, so @@ -1845,7 +1850,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool { InvertedLock ung (m_GroupLock); enterCS(CUDT::uglobal().m_GlobControlLock); - HLOGC(gslog.Debug, log << "grp/sendBroadcast: Locked GlobControlLock, locking back GroupLock"); + HLOGC(gslog.Debug, log << "grp/sendSelectable: Locked GlobControlLock, locking back GroupLock"); } // Under this condition, as an unlock-lock cycle was done on m_GroupLock, @@ -1902,7 +1907,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool // Re-check after the waiting lock has been reacquired if (m_bClosing) { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: GROUP CLOSED, ABANDONING"); + HLOGC(gslog.Debug, log << "grp/sendSelectable: GROUP CLOSED, ABANDONING"); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } @@ -1939,7 +1944,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); } - HLOGC(gslog.Debug, log << "grp/sendBroadcast: all blocked, trying to common-block on epoll..."); + HLOGC(gslog.Debug, log << "grp/sendSelectable: all blocked, trying to common-block on epoll..."); // XXX TO BE REMOVED. Sockets should be subscribed in m_SndEID at connecting time // (both srt_connect and srt_accept). @@ -1961,7 +1966,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool { // Lift the group lock for a while, to avoid possible deadlocks. InvertedLock ug(m_GroupLock); - HLOGC(gslog.Debug, log << "grp/sendBroadcast: blocking on any of blocked sockets to allow sending"); + HLOGC(gslog.Debug, log << "grp/sendSelectable: blocking on any of blocked sockets to allow sending"); // m_iSndTimeOut is -1 by default, which matches the meaning of waiting forever THREAD_PAUSED(); @@ -2066,7 +2071,7 @@ int CUDTGroup::sendSelectable(const char* buf, int len, SRT_MSGCTRL& w_mc, bool if (none_succeeded) { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: all links broken (none succeeded to send a payload)"); + HLOGC(gslog.Debug, log << "grp/sendSelectable: all links broken (none succeeded to send a payload)"); m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); // Reparse error code, if set. @@ -5846,6 +5851,12 @@ int CUDTGroup::configure(const char* str) CUDTGroup::gli_t CUDTGroup::linkSelect_plain(const CUDTGroup::BalancingLinkState& state) { + if (m_Group.empty()) + { + // Should be impossible, but fallback just in case. + return m_Group.end(); + } + if (state.ilink == m_Group.end()) { // Very first sending operation. Pick up the first link @@ -5867,17 +5878,22 @@ CUDTGroup::gli_t CUDTGroup::linkSelect_plain(const CUDTGroup::BalancingLinkState // and this one isn't usable either, return m_Group.end(). if (this_link->sndstate == SRT_GST_IDLE) + { + HLOGC(gmlog.Debug, log << "linkSelect_plain: activating link [" << distance(m_Group.begin(), this_link) << "] @" << this_link->id); this_link->sndstate = SRT_GST_RUNNING; + } if (this_link->sndstate == SRT_GST_RUNNING) { // Found you, buddy. Go on. + HLOGC(gmlog.Debug, log << "linkSelect_plain: SELECTING link [" << distance(m_Group.begin(), this_link) << "] @" << this_link->id); return this_link; } if (this_link == state.ilink) { // No more links. Sorry. + HLOGC(gmlog.Debug, log << "linkSelect_plain: rolled back to first link not running - bailing out"); return m_Group.end(); } @@ -6134,18 +6150,24 @@ bool CUDTGroup::updateSendPacketUnique_LOCKED(int32_t single_seq) for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d) { if (find(d->send_schedule.begin(), d->send_schedule.end(), (SchedSeq){single_seq, groups::SQT_FRESH}) != d->send_schedule.end()) + { + HLOGC(gmlog.Debug, log << "grp/schedule(fresh): already scheduled to %" << d->id << " - skipping"); return true; // because this should be considered successful, even though didn't schedule. + } } BalancingLinkState lstate = { m_Group.active(), 0, 0 }; gli_t selink = CALLBACK_CALL(m_cbSelectLink, lstate); if (selink == m_Group.end()) { + HLOGC(gmlog.Debug, log << "grp/schedule(fresh): no link selected!"); // If this returns the "trap" link index, it means // that no link is qualified for sending. return false; } + HLOGC(gmlog.Debug, log << "grp/schedule(fresh): scheduling %" << single_seq << " to @" << selink->id); + selink->send_schedule.push_back((groups::SchedSeq){single_seq, groups::SQT_FRESH}); m_Group.set_active(selink); @@ -6170,14 +6192,22 @@ bool CUDTGroup::updateSendPacketLoss(bool use_send_sched, const std::vector< std int num = 0; // for stats + HLOGC(gslog.Debug, log << "INITIAL:"); + HLOGC(gslog.Debug, m_pSndLossList->traceState(log)); + // Add the loss list to the groups loss list for (seqlist_t::const_iterator seqpair = seqlist.begin(); seqpair != seqlist.end(); ++seqpair) { - num += m_pSndLossList->insert(seqpair->first, seqpair->second); + int len = m_pSndLossList->insert(seqpair->first, seqpair->second); + num += len; + HLOGC(gslog.Debug, log << "LOSS Added: " << Printable(seqlist) << " length: " << len); + HLOGC(gslog.Debug, m_pSndLossList->traceState(log)); } if (use_send_sched) { + ScopedLock guard(m_GroupLock); + BalancingLinkState lstate = { m_Group.active(), 0, 0 }; for (seqlist_t::const_iterator seqpair = seqlist.begin(); seqpair != seqlist.end(); ++seqpair) @@ -6196,9 +6226,11 @@ bool CUDTGroup::updateSendPacketLoss(bool use_send_sched, const std::vector< std if (selink == m_Group.end()) { // Interrupt all - we have no link candidates to send. + HLOGC(gmlog.Debug, log << "grp/schedule(loss): no link selected!"); return false; } + HLOGC(gmlog.Debug, log << "grp/schedule(loss): schedule REXMIT %" << seq << " to @" << selink->id); selink->send_schedule.push_back((SchedSeq){seq, groups::SQT_LOSS}); lstate.ilink = selink; } @@ -6209,8 +6241,14 @@ bool CUDTGroup::updateSendPacketLoss(bool use_send_sched, const std::vector< std return true; } -void CUDTGroup::updateSndLossListOnACK(int32_t ackdata_seqno) +bool CUDTGroup::updateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seqno) { + w_last_sent_seqno = getSentSeq(); + /* + if (CSeqNo::seqcmp(ackdata_seqno, w_last_sent_seqno) > 0) + return false; + */ + ScopedLock guard(m_LossAckLock); if (CSeqNo::seqcmp(m_SndLastDataAck, ackdata_seqno) < 0) { @@ -6218,6 +6256,8 @@ void CUDTGroup::updateSndLossListOnACK(int32_t ackdata_seqno) m_pSndLossList->removeUpTo(CSeqNo::decseq(ackdata_seqno)); m_SndLastDataAck = ackdata_seqno; } + + return true; } // This is almost a copy of the CUDT::packLostData except that: @@ -6245,7 +6285,7 @@ int CUDTGroup::packLostData(CUDT* core, CPacket& w_packet, steady_clock::time_po have_extracted = m_pSndLossList->popLostSeq(exp_seq); } - HLOGC(qslog.Debug, log << "CUDTGroup::packLostData: " << (have_extracted ? "" : "NOT") << " extracted " + HLOGC(gslog.Debug, log << "CUDTGroup::packLostData: " << (have_extracted ? "" : "NOT") << " extracted " << as << " %" << exp_seq); if (have_extracted) @@ -6261,7 +6301,7 @@ int CUDTGroup::packLostData(CUDT* core, CPacket& w_packet, steady_clock::time_po // 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 << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo " + LOGC(gslog.Error, log << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo " << w_packet.m_iSeqNo << ", m_iSndLastDataAck " << core->m_iSndLastDataAck << ")=" << offset << ". Continue"); @@ -6274,7 +6314,7 @@ int CUDTGroup::packLostData(CUDT* core, CPacket& w_packet, steady_clock::time_po }; w_packet.m_iMsgNo = 0; // Message number is not known, setting all 32 bits to 0. - HLOGC(qslog.Debug, log << "PEER reported LOSS not from the sending buffer - requesting DROP: " + HLOGC(gslog.Debug, log << "PEER reported LOSS not from the sending buffer - requesting DROP: " << "msg=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " SEQ:" << seqpair[0] << " - " << seqpair[1] << "(" << (-offset) << " packets)"); @@ -6291,7 +6331,7 @@ int CUDTGroup::packLostData(CUDT* core, CPacket& w_packet, steady_clock::time_po SRT_ASSERT(msglen >= 1); seqpair[1] = CSeqNo::incseq(seqpair[0], msglen - 1); - HLOGC(qslog.Debug, + HLOGC(gslog.Debug, log << "loss-reported packets expired in SndBuf - requesting DROP: " << "msgno=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " msglen=" << msglen << " SEQ:" << seqpair[0] << " - " << seqpair[1]); @@ -6328,7 +6368,7 @@ int CUDTGroup::packLostData(CUDT* core, CPacket& w_packet, steady_clock::time_po else { // This is not the sequence we are looking for. - HLOGC(qslog.Debug, log << "packLostData: expected %" << exp_seq << " not found in the group's loss list"); + HLOGC(gslog.Debug, log << "packLostData: expected %" << exp_seq << " not found in the group's loss list"); } return 0; @@ -6355,16 +6395,18 @@ void CUDTGroup::discardSendSchedule(SocketData* d, int ndiscard) ScopedLock glock (m_GroupLock); if (ndiscard > int(d->send_schedule.size())) { - // XXX report error + LOGC(gmlog.Error, log << "grp/discardSendSchedule: IPE: size " << ndiscard << " is out of range of " << d->send_schedule.size() << " (fallback: clear all)"); d->send_schedule.clear(); } else if (ndiscard == int(d->send_schedule.size())) { + HLOGC(gmlog.Debug, log << "grp/discardSendSchedule: clear all"); d->send_schedule.clear(); } else { d->send_schedule.erase(d->send_schedule.begin(), d->send_schedule.begin() + ndiscard); + HLOGC(gmlog.Debug, log << "grp/discardSendSchedule: drop " << ndiscard << " and keep " << d->send_schedule.size() << " events"); } } diff --git a/srtcore/group.h b/srtcore/group.h index d70f74b25..d0e621554 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -464,6 +464,8 @@ class CUDTGroup void erase(gli_t it); }; + + SRT_ATTR_PT_GUARDED_BY(m_GroupLock) GroupContainer m_Group; SRT_GROUP_TYPE m_type; #if !ENABLE_NEW_RCVBUFFER @@ -732,6 +734,35 @@ class CUDTGroup sync::atomic m_RcvLastSeqNo; sync::atomic m_SndLastDataAck; + // This is required as a value with its own lock. + // There's no point in locking the whole group for this, + // but for a Sequence type data atomic is not enough + // because the update uses a roll-number comparison. + // It is however enough for reading the current value. + + sync::Mutex m_SndLastSeqLock; + + SRT_ATTR_GUARDED_BY(m_iSndLastSeqLock) + sync::atomic m_SndLastSeqNo; + +public: + int32_t updateSentSeq(int32_t seqno) + { + sync::ScopedLock lk(m_SndLastSeqLock); + + if (CSeqNo::seqcmp(seqno, m_SndLastSeqNo) > 0) + { + m_SndLastSeqNo = seqno; + } + return m_SndLastSeqNo; + } + + int32_t getSentSeq() const + { + return m_SndLastSeqNo; + } + +private: #else void recv_CollectAliveAndBroken(std::vector& w_alive, std::set& w_broken); @@ -970,7 +1001,7 @@ class CUDTGroup SRT_ATR_NODISCARD bool getSendSchedule(SocketData* d, std::vector& w_sched); void discardSendSchedule(SocketData* d, int ndiscard); - void updateSndLossListOnACK(int32_t ackdata_seqno); + bool updateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seqno); int packLostData(CUDT* core, CPacket& w_packet, steady_clock::time_point& w_origintime, int32_t exp_seq); time_point getPktTsbPdTime(uint32_t usPktTimestamp) const diff --git a/srtcore/group_common.cpp b/srtcore/group_common.cpp index fae27c80c..e9bc13e11 100644 --- a/srtcore/group_common.cpp +++ b/srtcore/group_common.cpp @@ -63,5 +63,12 @@ SocketData prepareSocketData(CUDTSocket* s, SRT_GROUP_TYPE type) return sd; } +// debug only. May crash if 's' runs out of range. +std::string SeqTypeStr(SeqType s) +{ + static const char* const name_table[4] = {"FRESH", "LOSS", "PFILTER", "SKIP"}; + return name_table[int(s)]; +} + } // namespace groups } // namespace srt diff --git a/srtcore/group_common.h b/srtcore/group_common.h index eaddfe296..fc9832310 100644 --- a/srtcore/group_common.h +++ b/srtcore/group_common.h @@ -47,6 +47,8 @@ namespace groups SQT_SKIP }; + std::string SeqTypeStr(SeqType); + struct SchedSeq { int32_t seq; diff --git a/srtcore/list.cpp b/srtcore/list.cpp index 3d4b375fa..9c1f13242 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -97,15 +97,7 @@ srt::CSndLossList::~CSndLossList() void srt::CSndLossList::traceState() const { - int pos = m_iHead; - while (pos != SRT_SEQNO_NONE) - { - std::cout << pos << ":[" << m_caSeq[pos].seqstart; - if (m_caSeq[pos].seqend != SRT_SEQNO_NONE) - std::cout << ", " << m_caSeq[pos].seqend; - std::cout << "], "; - pos = m_caSeq[pos].inext; - } + traceState(std::cout); std::cout << "\n"; } @@ -170,12 +162,14 @@ int srt::CSndLossList::insert(int32_t seqlo, int32_t seqhi) if (offset < 0) { + HLOGC(qslog.Debug, log << "CSndLossList::insert: offset=" << offset << " - inserting at head"); insertHead(loc, seqlo, seqhi); } else if (offset > 0) { if (seqlo == m_caSeq[loc].seqstart) { + HLOGC(qslog.Debug, log << "CSndLossList::insert: offset=" << offset << " - %" << seqlo << " found at [" << loc << "] - updating"); const bool updated = updateElement(loc, seqlo, seqhi); if (!updated) return 0; @@ -193,6 +187,8 @@ int srt::CSndLossList::insert(int32_t seqlo, int32_t seqhi) while (m_caSeq[i].inext != -1 && CSeqNo::seqcmp(m_caSeq[m_caSeq[i].inext].seqstart, seqlo) < 0) i = m_caSeq[i].inext; + HLOGC(qslog.Debug, log << "CSndLossList::insert: offset=" << offset << " - for [" << loc << "] prior node [" << i << "] - inserting"); + // 3. Check if seqlo overlaps with (seqbegin, seqend) const int seqend = m_caSeq[i].seqend == SRT_SEQNO_NONE ? m_caSeq[i].seqstart : m_caSeq[i].seqend; @@ -435,33 +431,46 @@ int srt::CSndLossList::rangecmp(int32_t seq, int32_t seqlo, int32_t seqhi) bool srt::CSndLossList::popLostSeq(int32_t seq) { ScopedLock listguard(m_ListLock); + HLOGC(qslog.Debug, log << "sndloss: try to extract %" << seq << " ..."); if (m_iLength == 0) + { + HLOGC(qslog.Debug, log << "... LOSS LIST EMPTY."); return false; // nothing in the container anyway + } if (seq == m_caSeq[m_iHead].seqstart) { // Pop the very first sequence. int seqr = popLostSeq_internal(); + HLOGC(qslog.Debug, log << "... FIRST MATCH, resolve to popLostSeq(), retrieved %" << seqr); + // All internal state has been modified accordingly. return (seqr == seq); } int loc = m_iHead; int* prev_next = &m_iHead; - for (int i = 0; i < m_iLength; ++i) + int prev_loc = -1; + + bool do_repeat = true; + for (;;) { + SRT_ASSERT(do_repeat); + Seq& cell = m_caSeq[loc]; + + // Minimum once must this loop be rolled after checked that m_iLength > 0. SRT_ASSERT(cell.seqstart != SRT_SEQNO_NONE); - // Ensure that if we've reached the last element by length - // then the next pointer is -1, and vice versa. - SRT_ASSERT((i == m_iLength - 1) ^ (cell.inext != -1)); + HLOGC(qslog.Debug, log << "... checking cell[" << loc << "] %" << cell.seqstart + << "/" << cell.seqend << " next=" << cell.inext); int cmp = rangecmp(seq, cell.seqstart, cell.seqend); if (cmp < 0) { + HLOGC(qslog.Debug, log << "... seq precedes range - considered NOT FOUND"); // Ranges collected here are increasing, so if this isn't present // in this range and precedes it, and all "previous" ranges have been // checked already, this means that this sequence isn't in the list. @@ -475,14 +484,22 @@ bool srt::CSndLossList::popLostSeq(int32_t seq) // Otherwise, this is possibly in any of the following loss ranges, so // unless we reached the end of list, if (cell.inext == -1) - break; + { + HLOGC(qslog.Debug, log << "... seq past the last item - considered NOT FOUND"); + return false; + } // continue with the next one. + HLOGC(qslog.Debug, log << "... take on the next cell[" << cell.inext << "]"); prev_next = &cell.inext; + prev_loc = loc; + loc = cell.inext; continue; } + do_repeat = false; + // You hit it right on the head. Now check optimistic edge. if (cell.seqend == SRT_SEQNO_NONE) @@ -491,6 +508,17 @@ bool srt::CSndLossList::popLostSeq(int32_t seq) // and rebind to the preceding element link. cell.seqstart = SRT_SEQNO_NONE; *prev_next = cell.inext; + + // Removing the current cell - so last insert is placed on the previous pos, + // if this one WAS the last insert pos. + if (m_iLastInsertPos == loc) + m_iLastInsertPos = prev_loc; + + SRT_ASSERT(prev_next != &m_iHead || (m_iHead != -1 || m_iLength == 0)); + + HLOGC(qslog.Debug, log << "... FOUND single - removing and setting prev[" << prev_loc << "].next=" << cell.inext); + cell.seqstart = cell.seqend = SRT_SEQNO_NONE; + cell.inext = LOC_NONE; } else if (seq == cell.seqend) { @@ -499,49 +527,101 @@ bool srt::CSndLossList::popLostSeq(int32_t seq) cell.seqend = CSeqNo::decseq(seq); if (cell.seqend == cell.seqstart) cell.seqend = SRT_SEQNO_NONE; + + // LAST INSERT POS = stays where it was. + + HLOGC(qslog.Debug, log << "... FOUND at end of %(" << cell.seqstart + << "-" << seq << "), slashing to %" << cell.seqend); } - else if (seq != cell.seqstart) + else if (seq == cell.seqstart) { - // We are in the middle, so the current - // element stays, just gets slashed, and - // the new element has to be created. - int offset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seq); - int newloc = (m_iHead + offset + m_iSize) % m_iSize; - m_caSeq[newloc].seqstart = CSeqNo::incseq(seq); + // This is the beginning sequence of the range containing + // more than 1 element. This means that we need to MOVE + // this element and update the previous element. + int32_t newbeginseq = CSeqNo::incseq(seq); + + int newoffset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, newbeginseq); + int newloc = (m_iHead + newoffset + m_iSize) % m_iSize; + + SRT_ASSERT(newloc != loc); + + m_caSeq[newloc].seqstart = newbeginseq; if (m_caSeq[newloc].seqstart != cell.seqend) { // If they were equal, seqend of the new cell should // remain cleared. If they are not, copy from the current cell. m_caSeq[newloc].seqend = cell.seqend; } + m_caSeq[newloc].inext = cell.inext; - // Ok, now update the upper range - cell.seqend = CSeqNo::decseq(seq); - if (cell.seqend == cell.seqstart) - cell.seqend = SRT_SEQNO_NONE; + HLOGC(qslog.Debug, log << "... FOUND at begin of %(" + << cell.seqstart << "/" << cell.seqend + << ") move [" << loc << "] to [" << newloc + << "] %(" << m_caSeq[newloc].seqstart + << "/" << m_caSeq[newloc].seqend << ") next=" + << cell.inext); + + // If the last insert pos was set on the found record, + // place it on the new record. + if (m_iLastInsertPos == loc) + m_iLastInsertPos = newloc; + + *prev_next = newloc; + cell.seqstart = SRT_SEQNO_NONE; + cell.seqend = SRT_SEQNO_NONE; + cell.inext = LOC_NONE; } else { - // This is the beginning sequence of the range containing - // more than 1 element. This means that we need to MOVE - // this element and update the previous element. + // We are in the middle, so the current + // element stays, just gets slashed, and + // the new element has to be created. - int offset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seq); - int newloc = (m_iHead + offset + m_iSize) % m_iSize; - m_caSeq[newloc].seqstart = CSeqNo::incseq(seq); + int32_t newbeginseq = CSeqNo::incseq(seq); + + int newoffset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, newbeginseq); + int newloc = (m_iHead + newoffset + m_iSize) % m_iSize; + + SRT_ASSERT(newloc != loc); + + m_caSeq[newloc].seqstart = newbeginseq; if (m_caSeq[newloc].seqstart != cell.seqend) { // If they were equal, seqend of the new cell should // remain cleared. If they are not, copy from the current cell. m_caSeq[newloc].seqend = cell.seqend; } + else + { + m_caSeq[newloc].seqend = SRT_SEQNO_NONE; + } + m_caSeq[newloc].inext = cell.inext; - *prev_next = newloc; - cell.seqstart = SRT_SEQNO_NONE; - cell.seqend = SRT_SEQNO_NONE; + // Ok, now update the upper range and bind with + // the next cell. + cell.seqend = CSeqNo::decseq(seq); + if (cell.seqend == cell.seqstart) + cell.seqend = SRT_SEQNO_NONE; + cell.inext = newloc; + + // If the last insert pos was set on the found record, + // place it on the new record. + if (m_iLastInsertPos == loc) + m_iLastInsertPos = newloc; + + HLOGC(qslog.Debug, log << "... FOUND inside of %(" + << cell.seqstart << "/" << cell.seqend + << ") split to [" << loc << "]=%(" << cell.seqstart + << "/" << cell.seqend << ") and [" << newloc + << "]=%(" << m_caSeq[newloc].seqstart + << "/" << m_caSeq[newloc].seqend << ") loc.next=" + << cell.inext << " newloc.next=" + << m_caSeq[newloc].inext); } m_iLength --; + + HLOGC(qslog.Debug, this->traceState(log)); return true; } diff --git a/srtcore/list.h b/srtcore/list.h index ed67afc1f..c8edb7175 100644 --- a/srtcore/list.h +++ b/srtcore/list.h @@ -76,7 +76,7 @@ class CSndLossList /// @param [in] seqno sequence number. void removeUpTo(int32_t seqno); - /// Read the loss length.∏ + /// Read the loss length. /// @return The length of the list. int getLossLength() const; @@ -89,8 +89,33 @@ class CSndLossList /// @return true if the sequence was found and removed, false otherwise. bool popLostSeq(int32_t seqno); + template + Stream& traceState(Stream& sout) const + { + int pos = m_iHead; + while (pos != SRT_SEQNO_NONE) + { + sout << "[" << pos << "]:" << m_caSeq[pos].seqstart; + if (m_caSeq[pos].seqend != SRT_SEQNO_NONE) + sout << ":" << m_caSeq[pos].seqend; + if (m_caSeq[pos].inext == -1) + sout << "=|"; + else + sout << "->[" << m_caSeq[pos].inext << "]"; + sout << ", "; + pos = m_caSeq[pos].inext; + } + sout << " {len:" << m_iLength << " head:" << m_iHead << " last:" << m_iLastInsertPos << "}"; + return sout; + } void traceState() const; + // Debug/unittest support. + + int head() const { return m_iHead; } + int next(int loc) const { return m_caSeq[loc].inext; } + int last() const { return m_iLastInsertPos; } + private: struct Seq { diff --git a/test/test_list.cpp b/test/test_list.cpp index 899c9004f..59a2e832a 100644 --- a/test/test_list.cpp +++ b/test/test_list.cpp @@ -23,12 +23,12 @@ class CSndLossListTest void CheckEmptyArray() { EXPECT_EQ(m_lossList->getLossLength(), 0); - EXPECT_EQ(m_lossList->popLostSeq(), -1); + EXPECT_EQ(m_lossList->popLostSeq(), SRT_SEQNO_NONE); } void CleanUpList() { - while (m_lossList->popLostSeq() != -1); + while (m_lossList->popLostSeq() != SRT_SEQNO_NONE); } CSndLossList* m_lossList; @@ -177,7 +177,7 @@ TEST_F(CSndLossListTest, BasicRemoveInListNodeHead01) // Remove up to element 4 m_lossList->removeUpTo(4); EXPECT_EQ(m_lossList->getLossLength(), 0); - EXPECT_EQ(m_lossList->popLostSeq(), -1); + EXPECT_EQ(m_lossList->popLostSeq(), SRT_SEQNO_NONE); CheckEmptyArray(); } @@ -226,7 +226,7 @@ TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead01) EXPECT_EQ(m_lossList->getLossLength(), 4); m_lossList->removeUpTo(5); EXPECT_EQ(m_lossList->getLossLength(), 0); - EXPECT_EQ(m_lossList->popLostSeq(), -1); + EXPECT_EQ(m_lossList->popLostSeq(), SRT_SEQNO_NONE); CheckEmptyArray(); } @@ -294,7 +294,7 @@ TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead06) EXPECT_EQ(m_lossList->getLossLength(), 10); m_lossList->removeUpTo(50); EXPECT_EQ(m_lossList->getLossLength(), 0); - EXPECT_EQ(m_lossList->popLostSeq(), -1); + EXPECT_EQ(m_lossList->popLostSeq(), SRT_SEQNO_NONE); CheckEmptyArray(); } @@ -328,7 +328,7 @@ TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead08) EXPECT_EQ(m_lossList->getLossLength(), 1); m_lossList->removeUpTo(6); EXPECT_EQ(m_lossList->getLossLength(), 0); - EXPECT_EQ(m_lossList->popLostSeq(), -1); + EXPECT_EQ(m_lossList->popLostSeq(), SRT_SEQNO_NONE); CheckEmptyArray(); } @@ -342,7 +342,7 @@ TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead09) EXPECT_EQ(m_lossList->insert(1, 2), 2); m_lossList->removeUpTo(6); EXPECT_EQ(m_lossList->getLossLength(), 0); - EXPECT_EQ(m_lossList->popLostSeq(), -1); + EXPECT_EQ(m_lossList->popLostSeq(), SRT_SEQNO_NONE); CheckEmptyArray(); } @@ -371,7 +371,7 @@ TEST_F(CSndLossListTest, BasicRemoveInListNotInNodeHead11) EXPECT_EQ(m_lossList->insert(1, 2), 2); m_lossList->removeUpTo(7); EXPECT_EQ(m_lossList->getLossLength(), 0); - EXPECT_EQ(m_lossList->popLostSeq(), -1); + EXPECT_EQ(m_lossList->popLostSeq(), SRT_SEQNO_NONE); CheckEmptyArray(); } @@ -387,7 +387,7 @@ TEST_F(CSndLossListTest, InsertRemoveInsert01) EXPECT_EQ(m_lossList->insert(1, 2), 2); m_lossList->removeUpTo(6); EXPECT_EQ(m_lossList->getLossLength(), 0); - EXPECT_EQ(m_lossList->popLostSeq(), -1); + EXPECT_EQ(m_lossList->popLostSeq(), SRT_SEQNO_NONE); CheckEmptyArray(); } @@ -525,7 +525,7 @@ TEST_F(CSndLossListTest, InsertFullListCoalesce) EXPECT_EQ(m_lossList->popLostSeq(), i); EXPECT_EQ(m_lossList->getLossLength(), CSndLossListTest::SIZE - i); } - EXPECT_EQ(m_lossList->popLostSeq(), -1); + EXPECT_EQ(m_lossList->popLostSeq(), SRT_SEQNO_NONE); EXPECT_EQ(m_lossList->getLossLength(), 0); CheckEmptyArray(); @@ -558,7 +558,7 @@ TEST_F(CSndLossListTest, InsertFullListNoCoalesce) EXPECT_EQ(m_lossList->getLossLength(), initial_length - i); } EXPECT_EQ(m_lossList->popLostSeq(), seqno_last); - EXPECT_EQ(m_lossList->popLostSeq(), -1); + EXPECT_EQ(m_lossList->popLostSeq(), SRT_SEQNO_NONE); EXPECT_EQ(m_lossList->getLossLength(), 0); CheckEmptyArray(); @@ -576,7 +576,7 @@ TEST_F(CSndLossListTest, InsertFullListNegativeOffset) EXPECT_EQ(m_lossList->popLostSeq(), i); EXPECT_EQ(m_lossList->getLossLength(), CSndLossListTest::SIZE - (i - 10000000 + 1)); } - EXPECT_EQ(m_lossList->popLostSeq(), -1); + EXPECT_EQ(m_lossList->popLostSeq(), SRT_SEQNO_NONE); EXPECT_EQ(m_lossList->getLossLength(), 0); CheckEmptyArray(); @@ -633,3 +633,136 @@ TEST_F(CSndLossListTest, InsertUpdateElement01) EXPECT_EQ(m_lossList->insert(2, 5), 0); EXPECT_EQ(m_lossList->getLossLength(), 8); } + +static void TraceRandomRemove(int32_t seq, CSndLossList* list, std::ostream& sout) +{ + bool rem = list->popLostSeq(seq); + sout << "REMOVED: " << seq << " (" << (rem ? "ok" : "FAILED") << ")\n"; +} + +static void TraceState(CSndLossList* list, std::ostream& sout) +{ + sout << "TRACE: "; + list->traceState(sout); + sout << endl; +} + +TEST_F(CSndLossListTest, RandomRemoval) +{ + using namespace std; + + int len = 0; + + ASSERT_EQ((len += m_lossList->insert(100, 100)), 1); + ASSERT_EQ(m_lossList->last(), 0); + ASSERT_EQ((len += m_lossList->insert(102, 102)), 2); + ASSERT_EQ(m_lossList->last(), 2); + ASSERT_EQ((len += m_lossList->insert(105, 110)), 8); + ASSERT_EQ(m_lossList->last(), 5); + ASSERT_EQ((len += m_lossList->insert(120, 121)), 10); + ASSERT_EQ(m_lossList->last(), 20); + ASSERT_EQ((len += m_lossList->insert(150, 150)), 11); + ASSERT_EQ(m_lossList->last(), 50); + + ASSERT_EQ(m_lossList->head(), 0); + + // One torn off check + ASSERT_EQ(m_lossList->next(5), 20); + + cout << "ADDED: [100, 102, 105...110, 120...121, 150]\n"; + TraceState(m_lossList, cout); + + // Cases: + + // 1. Remove one-seq record + // 2. Remove 3-seq record: + // 2.a. remove first + // 2.b. remove last + // 2.c. remove middle + // 3. Remove 2-seq record: + // 3.a. remove first + // 3.b. remove last + + // Cross-case: + // See how removal of a complete record influences the others, as + // 1. 3-seq record + // 2. 2-seq record + // 3. single record + + // Cross-case: + // 1. After removal, records remain intact with only changed length. + // 2. After removal, the current record gets moved to a different place. + // 3. After removal, the record is deleted + // 4. After temoval the current record is split in half + + ASSERT_EQ(m_lossList->getLossLength(), 11); + + // (1) + (1) + (0) + TraceRandomRemove(102, m_lossList, cout); + TraceState(m_lossList, cout); + + ASSERT_EQ(m_lossList->getLossLength(), 10); + + // (2c) + (0) + (2) + TraceRandomRemove(106, m_lossList, cout); + TraceState(m_lossList, cout); + + ASSERT_EQ(m_lossList->getLossLength(), 9); + + TraceRandomRemove(109, m_lossList, cout); + TraceState(m_lossList, cout); + + ASSERT_EQ(m_lossList->getLossLength(), 8); + + // (2a) + TraceRandomRemove(107, m_lossList, cout); + TraceState(m_lossList, cout); + + ASSERT_EQ(m_lossList->getLossLength(), 7); + + TraceRandomRemove(100, m_lossList, cout); + TraceState(m_lossList, cout); + + ASSERT_EQ(m_lossList->getLossLength(), 6); + + TraceRandomRemove(150, m_lossList, cout); + TraceState(m_lossList, cout); + + ASSERT_EQ(m_lossList->getLossLength(), 5); + + // After the last node removal, last-insert pos + // should be shifted. + EXPECT_EQ(m_lossList->last(), 20); + + // (2b) + TraceRandomRemove(110, m_lossList, cout); + TraceState(m_lossList, cout); + + ASSERT_EQ(m_lossList->getLossLength(), 4); + + // (2b) + (2) + (1) + TraceRandomRemove(121, m_lossList, cout); + TraceState(m_lossList, cout); + + ASSERT_EQ(m_lossList->getLossLength(), 3); + + TraceRandomRemove(105, m_lossList, cout); + TraceState(m_lossList, cout); + + ASSERT_EQ(m_lossList->getLossLength(), 2); + + TraceRandomRemove(120, m_lossList, cout); + TraceState(m_lossList, cout); + + ASSERT_EQ(m_lossList->getLossLength(), 1); + + TraceRandomRemove(100, m_lossList, cout); + TraceState(m_lossList, cout); + + // Nothing removed, the list remains untouched + ASSERT_EQ(m_lossList->getLossLength(), 1); + + EXPECT_EQ(m_lossList->last(), 8); // After removal of 107! + +} + diff --git a/testing/srt-test-relay.cpp b/testing/srt-test-relay.cpp old mode 100644 new mode 100755 diff --git a/testing/testmedia.cpp b/testing/testmedia.cpp old mode 100644 new mode 100755 From 1f8faac7cfcaab6c440525142bac33ea6801f664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 24 Oct 2022 09:17:24 +0200 Subject: [PATCH 21/62] Improved and fixed balancing loss detection --- srtcore/core.cpp | 10 ++- srtcore/group.cpp | 160 +++++++++++++++++++++++++++++++++++++-- srtcore/group.h | 42 ++++++++-- srtcore/group_common.cpp | 2 + srtcore/group_common.h | 8 +- srtcore/logging.h | 2 +- srtcore/packet.cpp | 113 +++++++++++++++++++-------- 7 files changed, 285 insertions(+), 52 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 335337fed..da71a81aa 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -10807,7 +10807,7 @@ bool srt::CUDT::handleGroupPacketReception(CUDTGroup* grp, const vector& // This is executed only when bonding is enabled and only // with the new buffer (in which case the buffer is in the group). // NOTE: this will lock ALSO the receiver buffer lock in the group - CRcvBufferNew::InsertInfo info = grp->addDataUnit(u, (w_srt_loss_seqs), (have_loss)); + CRcvBufferNew::InsertInfo info = grp->addDataUnit(m_parent->m_GroupMemberData, u, (w_srt_loss_seqs), (have_loss)); if (info.result == CRcvBufferNew::InsertInfo::DISCREPANCY) { @@ -11007,10 +11007,9 @@ int srt::CUDT::processData(CUnit* in_unit) HLOGC(qrlog.Debug, log << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() << " seq=" << packet.getSeqNo() - // XXX FIX IT. OTS should represent the original sending time, but it's relative. - //<< " OTS=" << FormatTime(packet.getMsgTimeStamp()) << " ETS=" << FormatTime(ets) - << " PTS=" << FormatTime(pts)); + << " PTS=" << FormatTime(pts) + << " NOW=" << FormatTime(m_tsLastRspTime.load())); } #endif @@ -11128,6 +11127,9 @@ int srt::CUDT::processData(CUnit* in_unit) log << "processData: IN-GROUP rcv state transition " << srt_log_grp_state[gi->rcvstate] << " -> RUNNING."); gi->rcvstate = SRT_GST_RUNNING; +#if ENABLE_NEW_BONDING + gkeeper.group->updateRcvRunningState(); +#endif } else { diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 3d877b4b4..d841acc71 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -254,6 +254,9 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) : m_Global(CUDT::uglobal()) , m_GroupID(-1) , m_PeerGroupID(-1) +#if ENABLE_NEW_RCVBUFFER + , m_zLongestDistance(0) +#endif , m_type(gtype) #if !ENABLE_NEW_RCVBUFFER , m_listener() @@ -316,6 +319,8 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) #if ENABLE_NEW_RCVBUFFER m_cbSelectLink.set(this, &CUDTGroup::linkSelect_plain_fw); #endif + + m_RcvFurthestPacketTime = steady_clock::now(); } #if ENABLE_NEW_RCVBUFFER @@ -333,6 +338,60 @@ void CUDTGroup::createBuffers(int32_t isn, const time_point& tsbpd_start_time, i m_pSndLossList.reset(new CSndLossList(flow_winsize * 2)); } + +/// Update the internal state after a single link has been switched to RUNNING state. +void CUDTGroup::updateRcvRunningState() +{ + ScopedLock lk (m_GroupLock); + + size_t nrunning; + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + if (gi->rcvstate == SRT_GST_RUNNING) + ++nrunning; + } + + m_Group.set_number_running(nrunning); +} + +void CUDTGroup::updateErasedLink() +{ + // When a link has been erased, reset the tracing data + // to enforce a situation that some new links have been + // added + if (m_Group.size() > 1) + { + updateRcvRunningState(); + } + + m_zLongestDistance = 0; + m_tdLongestDistance = duration::zero(); +} + +void CUDTGroup::updateInterlinkDistance() +{ + // Before locking anything, check if you have good enough conditions + // to update the distance information. If not all links are idle, resolve + // to the distance equal to the number of links. That is, that many packets + // may be received after the gap so that the gap can be qualified as loss. + + if (m_Group.number_running() < m_Group.size()) + { + size_t max_size = max(m_zLongestDistance.load(), m_Group.size()); + m_zLongestDistance = max_size; + + // Reset the duration so that it's not being traced + m_tdLongestDistance = duration::zero(); + + // Can't do anything more. + return; + } + + ScopedLock lk (m_GroupLock); + + +} + #endif CUDTGroup::~CUDTGroup() @@ -382,6 +441,7 @@ void CUDTGroup::GroupContainer::erase(CUDTGroup::gli_t it) } } m_List.erase(it); + --m_sizeCache; } void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) @@ -1005,7 +1065,7 @@ void CUDTGroup::syncWithFirstSocket(const CUDT& core, const HandshakeSide side) #if ENABLE_NEW_RCVBUFFER -CRcvBufferNew::InsertInfo CUDTGroup::addDataUnit(CUnit* u, CUDT::loss_seqs_t& w_losses, bool& w_have_loss) +CRcvBufferNew::InsertInfo CUDTGroup::addDataUnit(groups::SocketData* member, CUnit* u, CUDT::loss_seqs_t& w_losses, bool& w_have_loss) { // If this returns false, the adding has failed and @@ -1019,7 +1079,7 @@ CRcvBufferNew::InsertInfo CUDTGroup::addDataUnit(CUnit* u, CUDT::loss_seqs_t& w_ if (info.result == CRcvBufferNew::InsertInfo::INSERTED) { - w_have_loss = checkPacketArrivalLoss(u->m_Packet, (w_losses)); + w_have_loss = checkPacketArrivalLoss(member, u->m_Packet, (w_losses)); } } @@ -1110,7 +1170,7 @@ void CUDTGroup::synchronizeLoss(int32_t seqno) } // [[using locked(m_RcvBufferLock)]] -bool CUDTGroup::checkPacketArrivalLoss(const CPacket& rpkt, CUDT::loss_seqs_t& w_losses) +bool CUDTGroup::checkPacketArrivalLoss(SocketData* member, const CPacket& rpkt, CUDT::loss_seqs_t& w_losses) { // This is called when the packet was added to the buffer and this // adding was successful. Here we need to: @@ -1155,6 +1215,37 @@ bool CUDTGroup::checkPacketArrivalLoss(const CPacket& rpkt, CUDT::loss_seqs_t& w { HLOGC(grlog.Debug, log << "grp:checkPacketArrivalLoss: latest updated: %" << m_RcvLastSeqNo << " -> %" << rpkt.m_iSeqNo); m_RcvLastSeqNo = rpkt.m_iSeqNo; + + // This should theoretically set it up with the very first packet received over whichever link + // but this time is initialized upon creation of the group, just in case. + m_RcvFurthestPacketTime = steady_clock::now(); + m_zLongestDistance = 0; // this member is at top + member->updateCounter = 0; + } + else + { + bool updated SRT_ATR_UNUSED = false; + if (++member->updateCounter == 10 && m_zLongestDistance > 1) + { + // Decrease by 1 once per 10 events so that if a link + // happens to deliver packets faster, it is at some point detected + // and taken into account. + --m_zLongestDistance; + m_tdLongestDistance = duration::zero(); + member->updateCounter = 0; + updated = true; + } + + int dist = CSeqNo::seqoff(rpkt.m_iSeqNo, m_RcvLastSeqNo); + dist = max(m_zLongestDistance, dist); + m_zLongestDistance = dist; + + duration td = steady_clock::now() - m_RcvFurthestPacketTime; + td = max(m_tdLongestDistance.load(), td); + m_tdLongestDistance = td; + + HLOGC(grlog.Debug, log << "grp:checkPacketArrivalLoss: latest = %" << m_RcvLastSeqNo << ": pkt %" << rpkt.m_iSeqNo + << " dist={" << dist << "pkt " << FormatDuration(m_tdLongestDistance) << (updated ? "} (reflected)" : "} (continued)")); } return have; @@ -1223,8 +1314,60 @@ bool CUDTGroup::checkBalancingLoss(const CPacket& pkt, CUDT::loss_seqs_t& w_loss map nums; FringeValues(followers, (nums)); - if (nums.size() >= m_Group.size() // 1 - || find_if(nums.begin(), nums.end(), FFringeGreaterThan(1)) != nums.end()) + IF_HEAVY_LOGGING(const char* which_condition[3] = {"fullcover", "longtail", "both???"}); + + bool longtail = false; + bool fullcover = nums.size() >= m_Group.number_running(); + if (!fullcover) + { + int actual_distance = CSeqNo::seqoff(m_iRcvPossibleLossSeq, m_RcvLastSeqNo); + + // The minimum distance is the number of links. + // This is used always, regardless of other conditions + longtail = (actual_distance > int(m_Group.size() + 1)); + + if (longtail && m_zLongestDistance > m_Group.size()) + { + // This is a complicated condition. We need to state that + // the long tail has been exceeded if: + // 1. We have a long distance measured. + // a. If not, fall back to the number of member links. + // + // 2. To this value we add 0.2 of the value (minimum 1) to make it + // a base value for test if this is exceeded. + // + // 3. We check the distance between the packet tested for + // being a loss (m_iRcvPossibleLossSeq) and the latest received + // (m_RcvLastSeqNo). + + int32_t basefax = m_zLongestDistance; + double extrafax = max(basefax * 0.2, 1.0); + basefax += int(extrafax); + + // Previously it was tested this way to find providers that are longer + // than given value (here 1). As we currently collect the measurement values + // as they appear, we don't need to check it now. + //find_if(nums.begin(), nums.end(), FFringeGreaterThan(1)) != nums.end(); + + longtail = (actual_distance > basefax); + + HLOGC(grlog.Debug, log << "grp:checkBalancingLoss: loss-distance=" << actual_distance + << (longtail ? " EXCEEDS" : " UNDER") << " the longest tail " << m_zLongestDistance + << " stretched to " << basefax); + } + else + { + HLOGC(grlog.Debug, log << "grp:checkBalancingLoss: loss-distance=" << actual_distance + << (longtail ? " EXCEEDS" : " BELOW") << " the group size=" << m_Group.size() + << (longtail ? " but not" : " and") << " the tail=" << m_zLongestDistance); + } + } + else + { + HLOGC(grlog.Debug, log << "grp:checkBalancingLoss: loss confirmed by " << nums.size() << " sources out of " << m_Group.number_running() << " running"); + } + + if (longtail || fullcover) { // Extract the whole first loss typename CUDT::loss_seqs_t::value_type loss; @@ -1242,7 +1385,7 @@ bool CUDTGroup::checkBalancingLoss(const CPacket& pkt, CUDT::loss_seqs_t& w_loss // Save the next found loss m_iRcvPossibleLossSeq = m_pRcvBuffer->getFirstLossSeq(CSeqNo::incseq(loss.second)); - HLOGC(gmlog.Debug, log << "... qualified as loss: %(" << loss.first << " - " << loss.second + HLOGC(gmlog.Debug, log << "... qualified as loss (" << which_condition[(int(fullcover) + 2*int(longtail))-1] << "): %(" << loss.first << " - " << loss.second << "), next loss: %" << m_iRcvPossibleLossSeq); if (m_iRcvPossibleLossSeq == SRT_SEQNO_NONE) @@ -1256,6 +1399,10 @@ bool CUDTGroup::checkBalancingLoss(const CPacket& pkt, CUDT::loss_seqs_t& w_loss more_losses = true; continue; } + else + { + HLOGC(gmlog.Debug, log << "... not yet a loss - waiting for possible sealing"); + } break; } @@ -5460,6 +5607,7 @@ int CUDTGroup::sendBalancing_orig(const char* buf, int len, SRT_MSGCTRL& w_mc) // the internal facilities won't know what to do with it anyway. // Simply delete the entry. m_Group.erase(d); + updateErasedLink(); continue; } broken_sockets.push_back(ps); diff --git a/srtcore/group.h b/srtcore/group.h index d0e621554..ea27b3c58 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -36,6 +36,7 @@ class CUDTGroup typedef sync::steady_clock::time_point time_point; typedef sync::steady_clock::duration duration; + typedef sync::AtomicDuration atomic_duration; typedef sync::steady_clock steady_clock; typedef groups::SocketData SocketData; typedef groups::SendBackupCtx SendBackupCtx; @@ -165,6 +166,7 @@ class CUDTGroup if (f != m_Group.end()) { m_Group.erase(f); + updateErasedLink(); // Reset sequence numbers on a dead group so that they are // initialized anew with the new alive connection within @@ -433,9 +435,16 @@ class CUDTGroup SRTSOCKET m_GroupID; SRTSOCKET m_PeerGroupID; + struct GroupContainer { + private: std::list m_List; + sync::atomic m_sizeCache; + +#if ENABLE_NEW_RCVBUFFER + sync::atomic m_zNumberRunning; +#endif /// This field is used only by some types of groups that need /// to keep track as to which link was lately used. Note that @@ -443,8 +452,14 @@ class CUDTGroup /// must be appropriately reset. gli_t m_LastActiveLink; + public: + GroupContainer() - : m_LastActiveLink(m_List.end()) + : m_sizeCache(0) +#if ENABLE_NEW_RCVBUFFER + , m_zNumberRunning(0) +#endif + , m_LastActiveLink(m_List.end()) { } @@ -454,17 +469,32 @@ class CUDTGroup gli_t begin() { return m_List.begin(); } gli_t end() { return m_List.end(); } bool empty() { return m_List.empty(); } - void push_back(const SocketData& data) { m_List.push_back(data); } + void push_back(const SocketData& data) { m_List.push_back(data); ++m_sizeCache; } void clear() { m_LastActiveLink = end(); m_List.clear(); + m_sizeCache = 0; } - size_t size() { return m_List.size(); } + size_t size() { return m_sizeCache; } void erase(gli_t it); + +#if ENABLE_NEW_RCVBUFFER + SRTU_PROPERTY_RW(size_t, number_running, m_zNumberRunning); +#endif }; +#if ENABLE_NEW_RCVBUFFER + sync::atomic m_zLongestDistance; + atomic_duration m_tdLongestDistance; +public: + void updateRcvRunningState(); + void updateErasedLink(); + void updateInterlinkDistance(); +private: +#endif + SRT_ATTR_PT_GUARDED_BY(m_GroupLock) GroupContainer m_Group; SRT_GROUP_TYPE m_type; @@ -732,6 +762,8 @@ class CUDTGroup #if ENABLE_NEW_RCVBUFFER sync::atomic m_RcvLastSeqNo; + sync::AtomicClock m_RcvFurthestPacketTime; + sync::atomic m_SndLastDataAck; // This is required as a value with its own lock. @@ -946,8 +978,8 @@ class CUDTGroup #if ENABLE_NEW_RCVBUFFER int checkLazySpawnLatencyThread(); - CRcvBufferNew::InsertInfo addDataUnit(CUnit* u, CUDT::loss_seqs_t&, bool&); - bool checkPacketArrivalLoss(const CPacket& rpkt, CUDT::loss_seqs_t&); + CRcvBufferNew::InsertInfo addDataUnit(SocketData* member, CUnit* u, CUDT::loss_seqs_t&, bool&); + bool checkPacketArrivalLoss(SocketData* member, const CPacket& rpkt, CUDT::loss_seqs_t&); bool checkBalancingLoss(const CPacket& rpkt, CUDT::loss_seqs_t&); int rcvDropTooLateUpTo(int32_t seqno); void synchronizeLoss(int32_t seqno); diff --git a/srtcore/group_common.cpp b/srtcore/group_common.cpp index e9bc13e11..eaff9d4c9 100644 --- a/srtcore/group_common.cpp +++ b/srtcore/group_common.cpp @@ -58,6 +58,8 @@ SocketData prepareSocketData(CUDTSocket* s, SRT_GROUP_TYPE type) 0, // unit_load 0, // weight 0, // pktSndDropTotal + 0, // rcvSeqDistance + 0, // updateCounter std::deque() // Could be {}, but in C++11. }; return sd; diff --git a/srtcore/group_common.h b/srtcore/group_common.h index fc9832310..9fe095a52 100644 --- a/srtcore/group_common.h +++ b/srtcore/group_common.h @@ -81,12 +81,14 @@ namespace groups double load_factor; double unit_load; - // Configuration uint16_t weight; - // Stats - int64_t pktSndDropTotal; + // Measurement + int64_t pktSndDropTotal; //< copy of socket's max drop stat value + int rcvSeqDistance; //< distance to the latest received sequence in the group + + size_t updateCounter; //< counter used to damper measurement pickup for longest sequence span // This is used only in balancing mode and it defines // sequence numbers of packets to be sent at the next request diff --git a/srtcore/logging.h b/srtcore/logging.h index e79785b46..683dada3e 100644 --- a/srtcore/logging.h +++ b/srtcore/logging.h @@ -74,7 +74,7 @@ written by #define HLOGP LOGP #define HLOGF LOGF -#define IF_HEAVY_LOGGING(instr) instr +#define IF_HEAVY_LOGGING(instr,...) instr,##__VA_ARGS__ #else diff --git a/srtcore/packet.cpp b/srtcore/packet.cpp index aacc37f2c..48c37c342 100644 --- a/srtcore/packet.cpp +++ b/srtcore/packet.cpp @@ -172,8 +172,10 @@ extern Logger inlog; } using namespace srt_logging; +namespace srt { + // Set up the aliases in the constructure -srt::CPacket::CPacket() +CPacket::CPacket() : m_extra_pad() , m_data_owned(false) , m_iSeqNo((int32_t&)(m_nHeader[SRT_PH_SEQNO])) @@ -194,12 +196,12 @@ srt::CPacket::CPacket() m_PacketVector[PV_DATA].set(NULL, 0); } -char* srt::CPacket::getData() +char* CPacket::getData() { return (char*)m_PacketVector[PV_DATA].dataRef(); } -void srt::CPacket::allocate(size_t alloc_buffer_size) +void CPacket::allocate(size_t alloc_buffer_size) { if (m_data_owned) { @@ -213,14 +215,14 @@ void srt::CPacket::allocate(size_t alloc_buffer_size) m_data_owned = true; } -void srt::CPacket::deallocate() +void CPacket::deallocate() { if (m_data_owned) delete[](char*) m_PacketVector[PV_DATA].data(); m_PacketVector[PV_DATA].set(NULL, 0); } -char* srt::CPacket::release() +char* CPacket::release() { // When not owned, release returns NULL. char* buffer = NULL; @@ -234,7 +236,7 @@ char* srt::CPacket::release() return buffer; } -srt::CPacket::~CPacket() +CPacket::~CPacket() { // PV_HEADER is always owned, PV_DATA may use a "borrowed" buffer. // Delete the internal buffer only if it was declared as owned. @@ -242,23 +244,71 @@ srt::CPacket::~CPacket() delete[](char*) m_PacketVector[PV_DATA].data(); } -size_t srt::CPacket::getLength() const +size_t CPacket::getLength() const { return m_PacketVector[PV_DATA].size(); } -void srt::CPacket::setLength(size_t len) +void CPacket::setLength(size_t len) { m_PacketVector[PV_DATA].setLength(len); } -void srt::CPacket::pack(UDTMessageType pkttype, const int32_t* lparam, void* rparam, size_t size) +#if ENABLE_HEAVY_LOGGING +// Debug only +static std::string FormatNumbers(UDTMessageType pkttype, const int32_t* lparam, void* rparam, size_t size) +{ + // This may be changed over time, so use special interpretation + // only for certain types, and still display all data, no matter + // if it is expected to provide anything or not. + std::ostringstream out; + + out << "ARG="; + if (lparam) + out << *lparam; + else + out << "none"; + + if (size == 0) + { + out << " [no data]"; + return out.str(); + } + else if (!rparam) + { + out << " [ {" << size << "} ]"; + return out.str(); + } + + bool interp_as_seq = (pkttype == UMSG_LOSSREPORT || pkttype == UMSG_DROPREQ); + + out << " [ "; + for (size_t i = 0; i < size; ++i) + { + int32_t val = ((int32_t*)rparam)[i]; + if (interp_as_seq) + { + if (val & LOSSDATA_SEQNO_RANGE_FIRST) + out << "<" << (val & (~LOSSDATA_SEQNO_RANGE_FIRST)) << ">"; + else + out << val; + } + else + { + out << std::showpos << std::hex << val << "/" << std::dec << val; + } + } + + out << "]"; + return out.str(); +} +#endif + +void CPacket::pack(UDTMessageType pkttype, const int32_t* lparam, void* rparam, size_t size) { // Set (bit-0 = 1) and (bit-1~15 = type) setControl(pkttype); - HLOGC(inlog.Debug, - log << "pack: type=" << MessageTypeStr(pkttype) << " ARG=" << (lparam ? Sprint(*lparam) : std::string("NULL")) - << " [ " << (rparam ? Sprint(*(int32_t*)rparam) : std::string()) << " ]"); + HLOGC(inlog.Debug, log << "pack: type=" << MessageTypeStr(pkttype) << FormatNumbers(pkttype, lparam, rparam, size)); // Set additional information and control information field switch (pkttype) @@ -364,7 +414,7 @@ void srt::CPacket::pack(UDTMessageType pkttype, const int32_t* lparam, void* rpa } } -void srt::CPacket::toNL() +void CPacket::toNL() { // XXX USE HtoNLA! if (isControl()) @@ -382,7 +432,7 @@ void srt::CPacket::toNL() } } -void srt::CPacket::toHL() +void CPacket::toHL() { // convert back into local host order uint32_t* p = m_nHeader; @@ -399,22 +449,22 @@ void srt::CPacket::toHL() } } -srt::IOVector* srt::CPacket::getPacketVector() +IOVector* CPacket::getPacketVector() { return m_PacketVector; } -srt::UDTMessageType srt::CPacket::getType() const +UDTMessageType CPacket::getType() const { return UDTMessageType(SEQNO_MSGTYPE::unwrap(m_nHeader[SRT_PH_SEQNO])); } -int srt::CPacket::getExtendedType() const +int CPacket::getExtendedType() const { return SEQNO_EXTTYPE::unwrap(m_nHeader[SRT_PH_SEQNO]); } -int32_t srt::CPacket::getAckSeqNo() const +int32_t CPacket::getAckSeqNo() const { // read additional information field // This field is used only in UMSG_ACK and UMSG_ACKACK, @@ -423,7 +473,7 @@ int32_t srt::CPacket::getAckSeqNo() const return m_nHeader[SRT_PH_MSGNO]; } -uint16_t srt::CPacket::getControlFlags() const +uint16_t CPacket::getControlFlags() const { // This returns exactly the "extended type" value, // which is not used at all in case when the standard @@ -432,17 +482,17 @@ uint16_t srt::CPacket::getControlFlags() const return SEQNO_EXTTYPE::unwrap(m_nHeader[SRT_PH_SEQNO]); } -srt::PacketBoundary srt::CPacket::getMsgBoundary() const +PacketBoundary CPacket::getMsgBoundary() const { return PacketBoundary(MSGNO_PACKET_BOUNDARY::unwrap(m_nHeader[SRT_PH_MSGNO])); } -bool srt::CPacket::getMsgOrderFlag() const +bool CPacket::getMsgOrderFlag() const { return 0 != MSGNO_PACKET_INORDER::unwrap(m_nHeader[SRT_PH_MSGNO]); } -int32_t srt::CPacket::getMsgSeq(bool has_rexmit) const +int32_t CPacket::getMsgSeq(bool has_rexmit) const { if (has_rexmit) { @@ -454,13 +504,13 @@ int32_t srt::CPacket::getMsgSeq(bool has_rexmit) const } } -bool srt::CPacket::getRexmitFlag() const +bool CPacket::getRexmitFlag() const { // return false; // return 0 != MSGNO_REXMIT::unwrap(m_nHeader[SRT_PH_MSGNO]); } -srt::EncryptionKeySpec srt::CPacket::getMsgCryptoFlags() const +EncryptionKeySpec CPacket::getMsgCryptoFlags() const { return EncryptionKeySpec(MSGNO_ENCKEYSPEC::unwrap(m_nHeader[SRT_PH_MSGNO])); } @@ -468,19 +518,19 @@ srt::EncryptionKeySpec srt::CPacket::getMsgCryptoFlags() const // This is required as the encryption/decryption happens in place. // This is required to clear off the flags after decryption or set // crypto flags after encrypting a packet. -void srt::CPacket::setMsgCryptoFlags(EncryptionKeySpec spec) +void CPacket::setMsgCryptoFlags(EncryptionKeySpec spec) { int32_t clr_msgno = m_nHeader[SRT_PH_MSGNO] & ~MSGNO_ENCKEYSPEC::mask; m_nHeader[SRT_PH_MSGNO] = clr_msgno | EncryptionKeyBits(spec); } -uint32_t srt::CPacket::getMsgTimeStamp() const +uint32_t CPacket::getMsgTimeStamp() const { // SRT_DEBUG_TSBPD_WRAP may enable smaller timestamp for faster wraparoud handling tests return (uint32_t)m_nHeader[SRT_PH_TIMESTAMP] & TIMESTAMP_MASK; } -srt::CPacket* srt::CPacket::clone() const +CPacket* CPacket::clone() const { CPacket* pkt = new CPacket; memcpy((pkt->m_nHeader), m_nHeader, HDR_SIZE); @@ -491,9 +541,6 @@ srt::CPacket* srt::CPacket::clone() const return pkt; } -namespace srt -{ - // Useful for debugging std::string PacketMessageFlagStr(uint32_t msgno_field) { @@ -522,10 +569,8 @@ inline void SprintSpecialWord(std::ostream& os, int32_t val) os << val; } -} // namespace srt - #if ENABLE_LOGGING -std::string srt::CPacket::Info() +std::string CPacket::Info() { std::ostringstream os; os << "TARGET=@" << m_iID << " "; @@ -580,3 +625,5 @@ std::string srt::CPacket::Info() return os.str(); } #endif + +} From 96e92dd558285ed484d69e2bbc611b2ff401fc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Tue, 25 Oct 2022 15:51:32 +0200 Subject: [PATCH 22/62] Adjusted to the latest changes in the group branch, per changes in master --- srtcore/core.cpp | 192 +++++++++++++++++++---------------------------- srtcore/core.h | 2 +- 2 files changed, 77 insertions(+), 117 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 4c50d30cf..1355daf01 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9870,14 +9870,9 @@ CUDT::time_point srt::CUDT::getPacketPTS(void*, const CPacket& packet) static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; -int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, bool& w_reorder_prevent_lossreport, CUDT::loss_seqs_t& w_srt_loss_seqs) +int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs) { - int initial_loss_ttl = 0; - if (m_bPeerRexmitFlag) - initial_loss_ttl = m_iReorderTolerance; - bool excessive SRT_ATR_UNUSED = true; // stays true unless it was successfully added - w_reorder_prevent_lossreport = false; // Loop over all incoming packets that were filtered out. // In case when there is no filter, there's just one packet in 'incoming', @@ -9940,38 +9935,29 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& // that exceeds the buffer size. Receiving data in this situation // is no longer possible and this is a point of no return. - LOGC(qrlog.Error, - log << CONID() << "SEQUENCE DISCREPANCY. BREAKING CONNECTION. seq=" << rpkt.m_iSeqNo - << " buffer=(" << m_iRcvLastAck << ":" << m_iRcvCurrSeqNo // -1 = size to last index - << "+" << CSeqNo::incseq(m_iRcvLastAck, int(m_pRcvBuffer->capacity()) - 1) << "), " - << (offset - avail_bufsize + 1) - << " past max. Reception no longer possible. REQUESTING TO CLOSE."); + LOGC(qrlog.Error, log << CONID() << + "SEQUENCE DISCREPANCY. BREAKING CONNECTION." + " seq=" << rpkt.m_iSeqNo + << " buffer=(" << m_iRcvLastSkipAck + << ":" << m_iRcvCurrSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, int(m_pRcvBuffer->capacity()) - 1) + << "), " << (offset-avail_bufsize+1) + << " past max. Reception no longer possible. REQUESTING TO CLOSE."); return -2; } else { -#if ENABLE_NEW_RCVBUFFER LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo << ", insert offset " << offset << ". " << m_pRcvBuffer->strFullnessState(m_iRcvLastAck, steady_clock::now()) ); -#else - LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo - << ", insert offset " << offset << ". " - << m_pRcvBuffer->strFullnessState(steady_clock::now()) - ); -#endif return -1; } } -#if ENABLE_NEW_RCVBUFFER buffer_add_result = m_pRcvBuffer->insert(u); -#else - buffer_add_result = m_pRcvBuffer->addData(u, offset); -#endif if (buffer_add_result < 0) { // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. @@ -10018,8 +10004,8 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& if (m_pRcvBuffer) { - bufinfo - << " avail=" << avail_bufsize + bufinfo << " BUFr=" << avail_bufsize + << " avail=" << getAvailRcvBufferSizeNoLock() << " buffer=(" << m_iRcvLastSkipAck << ":" << m_iRcvCurrSeqNo // -1 = size to last index << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, m_pRcvBuffer->capacity()-1) @@ -10045,35 +10031,15 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& HLOGC(qrlog.Debug, log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo)); - // Lame way to make an optional variable. - bool have_loss = false; - if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. { int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); - have_loss = true; w_srt_loss_seqs.push_back(make_pair(seqlo, seqhi)); HLOGC(qrlog.Debug, log << "pkt/LOSS DETECTED: %" << seqlo << " - %" << seqhi); } - if (initial_loss_ttl && have_loss) - { - // pack loss list for (possibly belated) NAK - // The LOSSREPORT will be sent in a while. - - ScopedLock lg(m_RcvLossLock); - for (loss_seqs_t::iterator i = w_srt_loss_seqs.begin(); i != w_srt_loss_seqs.end(); ++i) - { - m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); - HLOGC(qrlog.Debug, - log << "FreshLoss: added sequences: %(" << i->first << "-" << i->second - << ") tolerance: " << initial_loss_ttl); - - } - w_reorder_prevent_lossreport = true; - } } // Update the current largest sequence number that has been received. @@ -10189,7 +10155,6 @@ int srt::CUDT::processData(CUnit* in_unit) loss_seqs_t srt_loss_seqs; vector incoming; bool was_sent_in_order = true; - bool reorder_prevent_lossreport = false; // If the peer doesn't understand REXMIT flag, send rexmit request // always immediately. @@ -10197,37 +10162,26 @@ int srt::CUDT::processData(CUnit* in_unit) if (m_bPeerRexmitFlag) initial_loss_ttl = m_iReorderTolerance; - // After introduction of packet filtering, the "recordable loss detection" - // does not exactly match the true loss detection. When a FEC filter is - // working, for example, then getting one group filled with all packet but - // the last one and the FEC control packet, in this special case this packet - // won't be notified at all as lost because it will be recovered by the - // filter immediately before anyone notices what happened (and the loss - // detection for the further functionality is checked only afterwards, - // and in this case the immediate recovery makes the loss to not be noticed - // at all). - // - // Because of that the check for losses must happen BEFORE passing the packet - // to the filter and before the filter could recover the packet before anyone - // notices :) - - if (packet.getMsgSeq() != SRT_MSGNO_CONTROL) // disregard filter-control packets, their seq may mean nothing - { - int diff = CSeqNo::seqoff(m_iRcvCurrPhySeqNo, packet.m_iSeqNo); - // Difference between these two sequence numbers is expected to be: - // 0 - duplicated last packet (theory only) - // 1 - subsequent packet (alright) - // <0 - belated or recovered packet - // >1 - jump over a packet loss (loss = seqdiff-1) + // Track packet loss in statistics early, because a packet filter (e.g. FEC) might recover it later on, + // supply the missing packet(s), and the loss will no longer be visible for the code that follows. + if (packet.getMsgSeq(m_bPeerRexmitFlag) != SRT_MSGNO_CONTROL) // disregard filter-control packets, their seq may mean nothing + { + const int diff = CSeqNo::seqoff(m_iRcvCurrPhySeqNo, packet.m_iSeqNo); + // Difference between these two sequence numbers is expected to be: + // 0 - duplicated last packet (theory only) + // 1 - subsequent packet (alright) + // <0 - belated or recovered packet + // >1 - jump over a packet loss (loss = seqdiff-1) if (diff > 1) { const int loss = diff - 1; // loss is all that is above diff == 1 + ScopedLock lg(m_StatsLock); const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); m_stats.rcvr.lost.count(stats::BytesPackets(loss * avgpayloadsz, (uint32_t) loss)); HLOGC(qrlog.Debug, - log << "LOSS STATS: n=" << loss << " SEQ: [" << CSeqNo::incseq(m_iRcvCurrPhySeqNo) << " " + log << CONID() << "LOSS STATS: n=" << loss << " SEQ: [" << CSeqNo::incseq(m_iRcvCurrPhySeqNo) << " " << CSeqNo::decseq(packet.m_iSeqNo) << "]"); } @@ -10256,20 +10210,21 @@ int srt::CUDT::processData(CUnit* in_unit) // This check is needed as after getting the lock the socket // could be potentially removed. It is however granted that as long // as gi is non-NULL iterator, the group does exist and it does contain - // this socket as member (that is, 'gi' cannot be a dangling iterator). + // this socket as member (that is, 'gi' cannot be a dangling pointer). if (gi != NULL) { if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely { HLOGC(qrlog.Debug, - log << "processData: IN-GROUP rcv state transition " << srt_log_grp_state[gi->rcvstate] + log << CONID() << "processData: IN-GROUP rcv state transition " << srt_log_grp_state[gi->rcvstate] << " -> RUNNING."); gi->rcvstate = SRT_GST_RUNNING; } else { - HLOGC(qrlog.Debug, log << "processData: IN-GROUP rcv state transition NOT DONE - state:" - << srt_log_grp_state[gi->rcvstate]); + HLOGC(qrlog.Debug, + log << CONID() << "processData: IN-GROUP rcv state transition NOT DONE - state:" + << srt_log_grp_state[gi->rcvstate]); } } } @@ -10284,7 +10239,7 @@ int srt::CUDT::processData(CUnit* in_unit) // Stuff this data into the filter m_PacketFilter.receive(in_unit, (incoming), (filter_loss_seqs)); HLOGC(qrlog.Debug, - log << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs) + log << CONID() << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs) << " loss to report, " << (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF" : "REPORT ONLY THOSE")); @@ -10306,7 +10261,6 @@ int srt::CUDT::processData(CUnit* in_unit) int res = handleSocketPacketReception(incoming, (new_inserted), (was_sent_in_order), - (reorder_prevent_lossreport), (srt_loss_seqs)); if (res == -2) @@ -10326,6 +10280,25 @@ int srt::CUDT::processData(CUnit* in_unit) return -1; } + if (!srt_loss_seqs.empty()) + { + ScopedLock lock(m_RcvLossLock); + + HLOGC(qrlog.Debug, + log << CONID() << "processData: RECORDING LOSS: " << Printable(srt_loss_seqs) + << " tolerance=" << initial_loss_ttl); + + for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i) + { + m_pRcvLossList->insert(i->first, i->second); + if (initial_loss_ttl) + { + // The LOSSREPORT will be sent after initial_loss_ttl. + m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); + } + } + } + // This is moved earlier after introducing filter because it shouldn't // be executed in case when the packet was rejected by the receiver buffer. // However now the 'excessive' condition may be true also in case when @@ -10370,37 +10343,21 @@ int srt::CUDT::processData(CUnit* in_unit) if (!srt_loss_seqs.empty()) { - // A loss is detected - { - // TODO: Can unlock rcvloss after m_pRcvLossList->insert(...)? - // And probably protect m_FreshLoss as well. - - HLOGC(qrlog.Debug, log << "processData: LOSS DETECTED, %: " << Printable(srt_loss_seqs) << " - RECORDING."); - // if record_loss == false, nothing will be contained here - // Insert lost sequence numbers to the receiver loss list - ScopedLock lg(m_RcvLossLock); - for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i) - { - // If loss found, insert them to the receiver loss list - m_pRcvLossList->insert(i->first, i->second); - } - } - const bool report_recorded_loss = !m_PacketFilter || m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS; - if (!reorder_prevent_lossreport && report_recorded_loss) + if (!initial_loss_ttl && report_recorded_loss) { - HLOGC(qrlog.Debug, log << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs)); + HLOGC(qrlog.Debug, log << CONID() << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs)); sendLossReport(srt_loss_seqs); } if (m_bTsbPd) { - HLOGC(qrlog.Debug, log << "loss: signaling TSBPD cond"); + HLOGC(qrlog.Debug, log << CONID() << "loss: signaling TSBPD cond"); CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); } else { - HLOGC(qrlog.Debug, log << "loss: socket is not TSBPD, not signaling"); + HLOGC(qrlog.Debug, log << CONID() << "loss: socket is not TSBPD, not signaling"); } } @@ -10411,12 +10368,12 @@ int srt::CUDT::processData(CUnit* in_unit) // With NEVER, nothing is to be reported. if (!filter_loss_seqs.empty()) { - HLOGC(qrlog.Debug, log << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs)); + HLOGC(qrlog.Debug, log << CONID() << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs)); sendLossReport(filter_loss_seqs); if (m_bTsbPd) { - HLOGC(qrlog.Debug, log << "loss: signaling TSBPD cond"); + HLOGC(qrlog.Debug, log << CONID() << "loss: signaling TSBPD cond"); CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); } } @@ -10781,7 +10738,7 @@ void srt::CUDT::dropFromLossLists(int32_t from, int32_t to) ScopedLock lg(m_RcvLossLock); m_pRcvLossList->remove(from, to); - HLOGF(qrlog.Debug, "%sTLPKTDROP seq %d-%d (%d packets)", CONID().c_str(), from, to, CSeqNo::seqoff(from, to)); + HLOGF(qrlog.Debug, "%sTLPKTDROP seq %d-%d (%d packets)", CONID().c_str(), from, to, CSeqNo::seqlen(from, to)); if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0) return; @@ -10883,12 +10840,12 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) // XXX ASSUMPTIONS: // [[using assert(packet.m_iID == 0)]] - HLOGC(cnlog.Debug, log << "processConnectRequest: received a connection request"); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: received a connection request"); if (m_bClosing) { m_RejectReason = SRT_REJ_CLOSE; - HLOGC(cnlog.Debug, log << "processConnectRequest: ... NOT. Rejecting because closing."); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ... NOT. Rejecting because closing."); return m_RejectReason; } @@ -10900,7 +10857,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) if (m_bBroken) { m_RejectReason = SRT_REJ_CLOSE; - HLOGC(cnlog.Debug, log << "processConnectRequest: ... NOT. Rejecting because broken."); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ... NOT. Rejecting because broken."); return m_RejectReason; } // When CHandShake::m_iContentSize is used in log, the file fails to link! @@ -10916,8 +10873,8 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) { m_RejectReason = SRT_REJ_ROGUE; HLOGC(cnlog.Debug, - log << "processConnectRequest: ... NOT. Wrong size: " << packet.getLength() << " (expected: " << exp_len - << ")"); + log << CONID() << "processConnectRequest: ... NOT. Wrong size: " << packet.getLength() + << " (expected: " << exp_len << ")"); return m_RejectReason; } @@ -10927,7 +10884,8 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) if (!packet.isControl(UMSG_HANDSHAKE)) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(cnlog.Error, log << "processConnectRequest: the packet received as handshake is not a handshake message"); + LOGC(cnlog.Error, + log << CONID() << "processConnectRequest: the packet received as handshake is not a handshake message"); return m_RejectReason; } @@ -10945,14 +10903,15 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) int32_t cookie_val = bake(addr); - HLOGC(cnlog.Debug, log << "processConnectRequest: new cookie: " << hex << cookie_val); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: new cookie: " << hex << cookie_val); // REQUEST:INDUCTION. // Set a cookie, a target ID, and send back the same as // RESPONSE:INDUCTION. if (hs.m_iReqType == URQ_INDUCTION) { - HLOGC(cnlog.Debug, log << "processConnectRequest: received type=induction, sending back with cookie+socket"); + HLOGC(cnlog.Debug, + log << CONID() << "processConnectRequest: received type=induction, sending back with cookie+socket"); // XXX That looks weird - the calculated md5 sum out of the given host/port/timestamp // is 16 bytes long, but CHandShake::m_iCookie has 4 bytes. This then effectively copies @@ -10981,7 +10940,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) hs.m_iType = SrtHSRequest::wrapFlags(true /*put SRT_MAGIC_CODE in HSFLAGS*/, m_config.iSndCryptoKeyLen); bool whether SRT_ATR_UNUSED = m_config.iSndCryptoKeyLen != 0; HLOGC(cnlog.Debug, - log << "processConnectRequest: " << (whether ? "" : "NOT ") + log << CONID() << "processConnectRequest: " << (whether ? "" : "NOT ") << " Advertising PBKEYLEN - value = " << m_config.iSndCryptoKeyLen); size_t size = packet.getLength(); @@ -10989,7 +10948,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) setPacketTS(packet, steady_clock::now()); // Display the HS before sending it to peer - HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING HS (i): " << hs.show()); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: SENDING HS (i): " << hs.show()); m_pSndQueue->sendto(addr, packet); return SRT_REJ_UNKNOWN; // EXCEPTION: this is a "no-error" code. @@ -11002,13 +10961,14 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) if (!hs.valid()) { - LOGC(cnlog.Error, log << "processConnectRequest: ROGUE HS RECEIVED. Rejecting"); + LOGC(cnlog.Error, log << CONID() << "processConnectRequest: ROGUE HS RECEIVED. Rejecting"); m_RejectReason = SRT_REJ_ROGUE; return SRT_REJ_ROGUE; } HLOGC(cnlog.Debug, - log << "processConnectRequest: received type=" << RequestTypeStr(hs.m_iReqType) << " - checking cookie..."); + log << CONID() << "processConnectRequest: received type=" << RequestTypeStr(hs.m_iReqType) + << " - checking cookie..."); if (hs.m_iCookie != cookie_val) { cookie_val = bake(addr, cookie_val, -1); // SHOULD generate an earlier, distracted cookie @@ -11016,15 +10976,15 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) if (hs.m_iCookie != cookie_val) { m_RejectReason = SRT_REJ_RDVCOOKIE; - HLOGC(cnlog.Debug, log << "processConnectRequest: ...wrong cookie " << hex << cookie_val << ". Ignoring."); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ...wrong cookie " << hex << cookie_val << ". Ignoring."); return m_RejectReason; } - HLOGC(cnlog.Debug, log << "processConnectRequest: ... correct (FIXED) cookie. Proceeding."); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ... correct (FIXED) cookie. Proceeding."); } else { - HLOGC(cnlog.Debug, log << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding."); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding."); } int32_t id = hs.m_iID; @@ -11069,15 +11029,15 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) if (!accepted_hs) { HLOGC(cnlog.Debug, - log << "processConnectRequest: version/type mismatch. Sending REJECT code:" << m_RejectReason - << " MSG: " << srt_rejectreason_str(m_RejectReason)); + log << CONID() << "processConnectRequest: version/type mismatch. Sending REJECT code:" << m_RejectReason + << " MSG: " << srt_rejectreason_str(m_RejectReason)); // mismatch, reject the request hs.m_iReqType = URQFailure(m_RejectReason); size_t size = CHandShake::m_iContentSize; hs.store_to((packet.m_pcData), (size)); packet.m_iID = id; setPacketTS(packet, steady_clock::now()); - HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING HS (e): " << hs.show()); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: SENDING HS (e): " << hs.show()); m_pSndQueue->sendto(addr, packet); } else diff --git a/srtcore/core.h b/srtcore/core.h index 72946a5bf..da113f93f 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1062,7 +1062,7 @@ class CUDT std::pair packData(CPacket& packet); int processData(CUnit* unit); - int handleSocketPacketReception(const std::vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, bool& w_reorder_prevent_loss, CUDT::loss_seqs_t& w_srt_loss_seqs); + int handleSocketPacketReception(const std::vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); // Group passed here by void* because in the current imp it's // unused and shall not be used in case when bonding is off From deeaf058f8b7a75e3576272707b328f7a86648c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Wed, 26 Oct 2022 16:22:40 +0200 Subject: [PATCH 23/62] Some cosmetic fixes after the update --- srtcore/api.cpp | 4 +- srtcore/buffer_rcv.h | 5 +- srtcore/core.cpp | 155 +++++++---------------------------------- srtcore/filelist.maf | 2 +- srtcore/group.cpp | 86 ++++++++++++++++------- srtcore/group.h | 21 +++--- srtcore/group_common.h | 8 +-- srtcore/list.cpp | 2 +- srtcore/packet.cpp | 2 +- 9 files changed, 110 insertions(+), 175 deletions(-) diff --git a/srtcore/api.cpp b/srtcore/api.cpp index ad0fdc905..a406e7d46 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -150,7 +150,7 @@ bool srt::CUDTSocket::readReady() { #if ENABLE_BONDING - // In the "new bonding" the reading from a socket + // If this is a group member socket, then reading // happens exclusively from the group and the socket is // only used as a connection point, packet dispatching // and single link management. Data buffering and hence @@ -2241,7 +2241,7 @@ int srt::CUDTUnited::select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSE } // XXX This may crash when a member socket is added to selectEx. -// Consider revising to prevent a socket from being used. +// Consider revising to prevent a member socket from being used. int srt::CUDTUnited::selectEx(const vector& fds, vector* readfds, vector* writefds, diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 14323347a..6a05e6673 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -11,7 +11,6 @@ #ifndef INC_SRT_BUFFER_RCV_H #define INC_SRT_BUFFER_RCV_H -// XXX move to buffer_tools.h ? #include "buffer_snd.h" // AvgBufSize #include "common.h" #include "queue.h" @@ -37,7 +36,7 @@ namespace srt // | | // | \___ m_iEndPos // | -// \___ m_iStartPos: first message to read +// \___ m_iStartPos: first packet position in the buffer // // m_pUnit[i]->status_: 0: free, 1: good, 2: read, 3: dropped (can be combined with read?) // @@ -49,7 +48,7 @@ namespace srt // // // m_iStartPos: the first packet that should be read (might be empty) -// m_iEndPos: the end of contiguous range. Empty if == m_iStartPos +// m_iEndPos: the end of contiguous range. Empty if m_iEndPos == m_iStartPos // m_iDropPos: a packet available for retrieval after a drop. If == m_iEndPos, no such packet. // // Operational rules: diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 7b337933c..6bdcc35b1 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -3367,19 +3367,18 @@ void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) if (!first_time) { - HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID - << " DERIVED ISN: RCV=%" << m_iRcvLastAck << " -> %" << rcv_isn - << " (shift by " << CSeqNo::seqcmp(rcv_isn, m_iRcvLastAck) - << ") SND=%" << m_iSndLastAck << " -> %" << snd_isn - << " (shift by " << CSeqNo::seqcmp(snd_isn, m_iSndLastAck) << ")"); + HLOGC(gmlog.Debug, + log << CONID() << "synchronizeWithGroup: DERIVED ISN: RCV=%" << m_iRcvLastAck << " -> %" << rcv_isn + << " (shift by " << CSeqNo::seqcmp(rcv_isn, m_iRcvLastAck) << ") SND=%" << m_iSndLastAck + << " -> %" << snd_isn << " (shift by " << CSeqNo::seqcmp(snd_isn, m_iSndLastAck) << ")"); setInitialRcvSeq(rcv_isn); setInitialSndSeq(snd_isn); } else { - HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID - << " DEFINED ISN: RCV=%" << m_iRcvLastAck - << " SND=%" << m_iSndLastAck); + HLOGC(gmlog.Debug, + log << CONID() << "synchronizeWithGroup: DEFINED ISN: RCV=%" << m_iRcvLastAck << " SND=%" + << m_iSndLastAck); } } #endif @@ -5485,14 +5484,11 @@ bool srt::CUDT::prepareBuffers(CUDTException *eout) // Keep the per-socket receiver buffer and receiver loss list empty. // Reception will be redirected to the group directly. if (!m_parent->m_GroupOf) +#endif { SRT_ASSERT(m_iISN != SRT_SEQNO_NONE); m_pRcvBuffer = new srt::CRcvBuffer(m_iISN, m_config.iRcvBufSize, m_config.bMessageAPI); } -#else - SRT_ASSERT(m_iISN != SRT_SEQNO_NONE); - m_pRcvBuffer = new srt::CRcvBuffer(m_iISN, m_config.iRcvBufSize, m_config.bMessageAPI); -#endif // after introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice space. m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2); m_pRcvLossList = new CRcvLossList(m_config.iFlightFlagSize); @@ -7615,39 +7611,6 @@ void srt::CUDT::ackDataUpTo(int32_t ack) m_iRcvLastSkipAck = ack; } -#if 0 // ENABLE_BONDING XXX unknown why blocked, investigate -void srt::CUDT::dropToGroupRecvBase() -{ - int32_t group_recv_base = SRT_SEQNO_NONE; - if (m_parent->m_GroupOf) - { - // Check is first done before locking to avoid unnecessary - // mutex locking. The condition for this field is that it - // can be either never set, already reset, or ever set - // and possibly dangling. The re-check after lock eliminates - // the dangling case. - ScopedLock glock (uglobal().m_GlobControlLock); - - // Note that getRcvBaseSeqNo() will lock m_GroupOf->m_GroupLock, - // but this is an intended order. - if (m_parent->m_GroupOf) - group_recv_base = m_parent->m_GroupOf->getRcvBaseSeqNo(); - } - if (group_recv_base == SRT_SEQNO_NONE) - return; - - ScopedLock lck(m_RcvBufferLock); - int cnt = rcvDropTooLateUpTo(CSeqNo::incseq(group_recv_base)); - if (cnt > 0) - { - HLOGC(grlog.Debug, - log << CONID() << "dropToGroupRecvBase: dropped " << cnt << " packets before ACK: group_recv_base=" - << group_recv_base << " m_iRcvLastSkipAck=" << m_iRcvLastSkipAck - << " m_iRcvCurrSeqNo=" << m_iRcvCurrSeqNo << " m_bTsbPd=" << m_bTsbPd); - } -} -#endif - namespace srt { #if ENABLE_HEAVY_LOGGING static void DebugAck(string hdr, int prev, int ack) @@ -7922,7 +7885,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { ackDataUpTo(ack); -/* WAS: #if ENABLE_OLD_BONDING +/* WAS: #if ENABLE_OLD_BONDING (used in the blocked code; to be removed in perspective) const int32_t group_read_seq = CSeqNo::decseq(ack); #endif */ @@ -8083,7 +8046,8 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) int32_t data[ACKD_TOTAL_SIZE]; // For "new bonding", still get this size from buffer, - // but only unless we have a group + // but only unless we have a group (in which case this value + // has been already set few lines earlier). if (!group_buffering) avail_receiver_buffer_size = (int) getAvailRcvBufferSizeNoLock(); @@ -8168,28 +8132,26 @@ void srt::CUDT::updateStateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seq #if ENABLE_BONDING - using namespace any_op; + // This method is necessary for balancing groups, but it can be as well + // used for BROADCAST groups, if it is decided that a loss reported on + // whichever link should be then retransmitted using all links (while + // assymmetric losses will not be reported). + // For BACKUP we stick to the individual per-socket + // loss handling as any sending on a different link than the current + // one should happen in case of possible breakup detection, so this + // way it doesn't make any sense to handle losses any other way than + // per socket, as usual. + + using namespace any_op; #ifdef BROADCAST_COMMON_SND_LOSS if (gkeeper.group && (EqualAny(gkeeper.group->type()), SRT_GTYPE_BALANCING, SRT_GTYPE_BROADCAST)) #else if (gkeeper.group && gkeeper.group->type() == SRT_GTYPE_BALANCING) #endif { - // XXX TEMPORARY added broadcast for testing. - - // For groups of type BALANCING we use the common loss handling. + // For groups of that type we use the common loss handling. - // XXX This method can be as well used for BROADCAST groups, if - // it is decided that a loss reported on whichever link should be - // then retransmitted using all links (while assymmetric losses - // will not be reported). - - // For BACKUP we stick to the individual per-socket - // loss handling as any sending on a different link than the current - // one should happen in case of possible breakup detection, so this - // way it doesn't make any sense to handle losses any other way than - // per socket, as usual. bool valid = gkeeper.group->updateOnACK(ackdata_seqno, (w_last_sent_seqno)); if (!valid) { @@ -8662,7 +8624,7 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { #if ENABLE_BONDING - // Keep group from disappearing in the meantime + // Keep the group from disappearing in the meantime CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); typedef vector< pair > losses_t; losses_t losses; @@ -9264,8 +9226,7 @@ void srt::CUDT::updateAfterSrtHandshake(int hsv) } } -// XXX w_origintime unused anymore? -int srt::CUDT::packLostData(CPacket& w_packet,/* steady_clock::time_point& w_origintime,*/ int32_t exp_seq) +int srt::CUDT::packLostData(CPacket& w_packet, int32_t exp_seq) { // protect m_iSndLastDataAck from updating by ACK processing UniqueLock ackguard(m_RecvAckLock); @@ -9504,7 +9465,6 @@ void srt::CUDT::setDataPacketTS(CPacket& p, const time_point& ts) setPacketTS(p, tsStart, ts); } - bool srt::CUDT::isRetransmissionAllowed(const time_point& tnow SRT_ATR_UNUSED) { // Prioritization of original packets only applies to Live CC. @@ -9816,7 +9776,6 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime HLOGC(qslog.Debug, log << CONID() << "filter: filter/CTL packet ready - packing instead of data."); payload = (int) w_packet.getLength(); IF_HEAVY_LOGGING(reason = "filter"); - /// XXX so? filter_ctl_pkt = true; // Mark that this packet ALREADY HAS timestamp field and it should not be set // Stats ScopedLock lg(m_StatsLock); @@ -11152,70 +11111,6 @@ void srt::CUDT::updateIdleLinkFrom(CUDT* source) } -// XXX This function is currently unused. It should be fixed and put into use. -// See the blocked call in CUDT::processData(). -// XXX REVIEW LOCKS WHEN REACTIVATING! - -/* -srt::CUDT::loss_seqs_t srt::CUDT::defaultPacketArrival(void* vself, CPacket& pkt) -{ -// [[using affinity(m_pRcvBuffer->workerThread())]]; - CUDT* self = (CUDT*)vself; - loss_seqs_t output; - - // XXX When an alternative packet arrival callback is installed - // in case of groups, move this part to the groupwise version. - - if (self->m_parent->m_GroupOf) - { - groups::SocketData* gi = self->m_parent->m_GroupMemberData; - if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely - { - HLOGC(qrlog.Debug, log << "defaultPacketArrival: IN-GROUP rcv state transition to RUNNING. NOT checking for loss"); - gi->rcvstate = SRT_GST_RUNNING; - return output; - } - } - - const int initial_loss_ttl = (self->m_bPeerRexmitFlag) ? self->m_iReorderTolerance : 0; - - int seqdiff = CSeqNo::seqcmp(pkt.m_iSeqNo, self->m_iRcvCurrSeqNo); - - HLOGC(qrlog.Debug, log << "defaultPacketArrival: checking sequence " << pkt.m_iSeqNo - << " against latest " << self->m_iRcvCurrSeqNo << " (distance: " << seqdiff << ")"); - - // Loss detection. - if (seqdiff > 1) // packet is later than the very subsequent packet - { - const int32_t seqlo = CSeqNo::incseq(self->m_iRcvCurrSeqNo); - const int32_t seqhi = CSeqNo::decseq(pkt.m_iSeqNo); - - { - // If loss found, insert them to the receiver loss list - ScopedLock lg (self->m_RcvLossLock); - self->m_pRcvLossList->insert(seqlo, seqhi); - - if (initial_loss_ttl) - { - // pack loss list for (possibly belated) NAK - // The LOSSREPORT will be sent in a while. - self->m_FreshLoss.push_back(CRcvFreshLoss(seqlo, seqhi, initial_loss_ttl)); - HLOGF(qrlog.Debug, "defaultPacketArrival: added loss sequence %d-%d (%d) with tolerance %d", seqlo, seqhi, - 1+CSeqNo::seqcmp(seqhi, seqlo), initial_loss_ttl); - } - } - - if (!initial_loss_ttl) - { - // old code; run immediately when tolerance = 0 - // or this feature isn't used because of the peer - output.push_back(make_pair(seqlo, seqhi)); - } - } - - return output; -} -*/ #endif /// This function is called when a packet has arrived, which was behind the current diff --git a/srtcore/filelist.maf b/srtcore/filelist.maf index 8dc6cf617..0f9b16fef 100644 --- a/srtcore/filelist.maf +++ b/srtcore/filelist.maf @@ -2,8 +2,8 @@ SOURCES api.cpp -buffer_rcv.cpp buffer_snd.cpp +buffer_rcv.cpp cache.cpp channel.cpp common.cpp diff --git a/srtcore/group.cpp b/srtcore/group.cpp index ef50835de..e53e36463 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -369,7 +369,7 @@ void CUDTGroup::GroupContainer::erase(CUDTGroup::gli_t it) } } m_List.erase(it); - --m_sizeCache; + --m_SizeCache; } void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) @@ -2351,19 +2351,9 @@ struct FLookupSocketWithEvent_LOCKED } }; -void CUDTGroup::updateWriteState() -{ - ScopedLock lg(m_GroupLock); - m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, true); -} - - -/* - - Old unused procedure. - Leaving here for historical reasons. - */ +// Old unused procedure. +// Leaving here for historical reasons. #if 0 void CUDTGroup::recv_CollectAliveAndBroken(vector& alive, set& broken) { @@ -2483,7 +2473,7 @@ vector CUDTGroup::recv_WaitForReadReady(const vector& // This call may wait indefinite time, so GroupLock must be unlocked. InvertedLock ung (m_GroupLock); THREAD_PAUSED(); - nready = m_Global.m_EPoll.swait(*m_RcvEpolld, sready, timeout, false /-report by retval-/); + nready = m_Global.m_EPoll.swait(*m_RcvEpolld, sready, timeout, false /*report by retval*/); THREAD_RESUMED(); // HERE GlobControlLock is locked first, then GroupLock is applied back @@ -2611,7 +2601,15 @@ int32_t CUDTGroup::getRcvBaseSeqNo() ScopedLock lg(m_GroupLock); return m_RcvBaseSeqNo; } +#endif +void CUDTGroup::updateWriteState() +{ + ScopedLock lg(m_GroupLock); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, true); +} + +#if 0 /// Validate iPktSeqno is in range /// (iBaseSeqno - m_iSeqNoTH/2; iBaseSeqno + m_iSeqNoTH). /// @@ -2834,8 +2832,58 @@ int CUDTGroup::recv_old(char* buf, int len, SRT_MSGCTRL& w_mc) m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } + +// [[using locked(m_GroupLock)]] +CUDTGroup::ReadPos* CUDTGroup::checkPacketAhead() +{ + typedef map::iterator pit_t; + ReadPos* out = 0; + + // This map no longer maps only ahead links. + // Here are all links, and whether ahead, it's defined by the sequence. + for (pit_t i = m_Positions.begin(); i != m_Positions.end(); ++i) + { + // i->first: socket ID + // i->second: ReadPos { sequence, packet } + // We are not interested with the socket ID because we + // aren't going to read from it - we have the packet already. + ReadPos& a = i->second; + + const int seqdiff = CSeqNo::seqcmp(a.mctrl.pktseq, m_RcvBaseSeqNo); + if (seqdiff == 1) + { + // The very next packet. Return it. + HLOGC(grlog.Debug, + log << "group/recv: Base %" << m_RcvBaseSeqNo << " ahead delivery POSSIBLE %" << a.mctrl.pktseq + << " #" << a.mctrl.msgno << " from @" << i->first << ")"); + out = &a; + } + else if (seqdiff < 1 && !a.packet.empty()) + { + HLOGC(grlog.Debug, + log << "group/recv: @" << i->first << " dropping collected ahead %" << a.mctrl.pktseq << "#" + << a.mctrl.msgno << " with base %" << m_RcvBaseSeqNo); + a.packet.clear(); + } + // In case when it's >1, keep it in ahead + } + + return out; +} + #endif // block by if 0 +const char* CUDTGroup::StateStr(CUDTGroup::GroupState st) +{ + static const char* const states[] = {"PENDING", "IDLE", "RUNNING", "BROKEN"}; + static const size_t size = Size(states); + static const char* const unknown = "UNKNOWN"; + if (size_t(st) < size) + return states[st]; + return unknown; +} + + // The REAL version for the new group receiver. // int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) @@ -3089,16 +3137,6 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) } -const char* CUDTGroup::StateStr(CUDTGroup::GroupState st) -{ - static const char* const states[] = {"PENDING", "IDLE", "RUNNING", "BROKEN"}; - static const size_t size = Size(states); - static const char* const unknown = "UNKNOWN"; - if (size_t(st) < size) - return states[st]; - return unknown; -} - void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) { if (!m_bConnected) diff --git a/srtcore/group.h b/srtcore/group.h index 08aee7670..66ca53f10 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -182,6 +182,9 @@ class CUDTGroup // number will collide with any ISN provided by a socket. // Also since now every socket will derive this ISN. m_iLastSchedSeqNo = generateISN(); + // XXX resetInitialRxSequence() was here, but + // this uses a field that is removed, and replaced by + // m_RcvLastSeqNo empty = true; } } @@ -432,9 +435,9 @@ class CUDTGroup struct GroupContainer { private: - std::list m_List; - sync::atomic m_sizeCache; - sync::atomic m_zNumberRunning; + std::list m_List; + sync::atomic m_SizeCache; + sync::atomic m_zNumberRunning; /// This field is used only by some types of groups that need /// to keep track as to which link was lately used. Note that @@ -445,7 +448,7 @@ class CUDTGroup public: GroupContainer() - : m_sizeCache(0) + : m_SizeCache(0) , m_zNumberRunning(0) , m_LastActiveLink(m_List.end()) { @@ -457,14 +460,14 @@ class CUDTGroup gli_t begin() { return m_List.begin(); } gli_t end() { return m_List.end(); } bool empty() { return m_List.empty(); } - void push_back(const SocketData& data) { m_List.push_back(data); ++m_sizeCache; } + void push_back(const SocketData& data) { m_List.push_back(data); ++m_SizeCache; } void clear() { m_LastActiveLink = end(); m_List.clear(); - m_sizeCache = 0; + m_SizeCache = 0; } - size_t size() { return m_sizeCache; } + size_t size() { return m_SizeCache; } void erase(gli_t it); @@ -788,8 +791,8 @@ class CUDTGroup sync::Condition m_RcvDataCond; sync::Mutex m_RcvDataLock; sync::Condition m_RcvTsbPdCond; - sync::atomic m_bTsbpdWaitForNewPacket; // TSBPD forever-wait should be signaled by new packet reception - sync::atomic m_bTsbpdWaitForExtraction;// TSBPD forever-wait should be signaled by extracting the last ready packet + sync::atomic m_bTsbpdWaitForNewPacket; // TSBPD forever-wait should be signaled by new packet reception + sync::atomic m_bTsbpdWaitForExtraction;// TSBPD forever-wait should be signaled by extracting the last ready packet mutable sync::Mutex m_RcvBufferLock; // Protects the state of the m_pRcvBuffer mutable sync::Mutex m_LossAckLock; diff --git a/srtcore/group_common.h b/srtcore/group_common.h index 1fd12702b..25be667ea 100644 --- a/srtcore/group_common.h +++ b/srtcore/group_common.h @@ -76,9 +76,9 @@ namespace groups bool ready_error; // Balancing data - bool use_send_schedule; - double load_factor; - double unit_load; + bool use_send_schedule; + double load_factor; + double unit_load; // Configuration uint16_t weight; @@ -87,7 +87,7 @@ namespace groups int64_t pktSndDropTotal; //< copy of socket's max drop stat value int rcvSeqDistance; //< distance to the latest received sequence in the group - size_t updateCounter; //< counter used to damper measurement pickup for longest sequence span + size_t updateCounter; //< counter used to damper measurement pickup for longest sequence span // This is used only in balancing mode and it defines // sequence numbers of packets to be sent at the next request diff --git a/srtcore/list.cpp b/srtcore/list.cpp index 670969e8d..3bb3d058a 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -403,7 +403,7 @@ int32_t srt::CSndLossList::popLostSeq_internal() /// It returns 0 in case when @a seq is in this range. Otherwise /// if it precedes this range, the returned value is the comparison /// result against @a seqlo, and if it succeeds the range, the -/// comparison result with @a seqhi, or, if it is SRT_SEQNO_NONE, +/// comparison result with @a seqhi, or, if seqhi == SRT_SEQNO_NONE, /// with @a seqlo. /// This function is using specific rules of CSndLossList. int srt::CSndLossList::rangecmp(int32_t seq, int32_t seqlo, int32_t seqhi) diff --git a/srtcore/packet.cpp b/srtcore/packet.cpp index 4c71b5da0..9dbb9ec4e 100644 --- a/srtcore/packet.cpp +++ b/srtcore/packet.cpp @@ -638,4 +638,4 @@ std::string CPacket::Info() } #endif -} +} // end namespace srt From 89e927f99daa1371289c77bd835bfc0c8c5726bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Wed, 26 Oct 2022 17:17:58 +0200 Subject: [PATCH 24/62] Added lacking fix for AEAD --- srtcore/core.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 72fee0e45..048d3f903 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9817,7 +9817,12 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& excessive = false; if (u->m_Packet.getMsgCryptoFlags() != EK_NOENC) { - EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt((u->m_Packet)) : ENCS_NOTSUP; + // TODO: reset and restore the timestamp if TSBPD is disabled. + // Reset retransmission flag (must be excluded from GCM auth tag). + u->m_Packet.setRexmitFlag(false); + const EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt((u->m_Packet)) : ENCS_NOTSUP; + u->m_Packet.setRexmitFlag(retransmitted); // Recover the flag. + if (rc != ENCS_CLEAR) { // Heavy log message because if seen once the message may happen very often. @@ -9825,6 +9830,21 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& adding_successful = false; IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); + if (m_config.iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM) + { + // Drop a packet from the receiver buffer. + // Dropping depends on the configuration mode. If message mode is enabled, we have to drop the whole message. + // Otherwise just drop the exact packet. + if (m_config.bMessageAPI) + m_pRcvBuffer->dropMessage(SRT_SEQNO_NONE, SRT_SEQNO_NONE, u->m_Packet.getMsgSeq(m_bPeerRexmitFlag)); + else + m_pRcvBuffer->dropMessage(u->m_Packet.getSeqNo(), u->m_Packet.getSeqNo(), SRT_MSGNO_NONE); + + LOGC(qrlog.Error, log << CONID() << "AEAD decryption failed, breaking the connection."); + m_bBroken = true; + m_iBrokenCounter = 0; + } + ScopedLock lg(m_StatsLock); m_stats.rcvr.undecrypted.count(stats::BytesPackets(rpkt.getLength(), 1)); } From a56c84d5b98e3b3e96d1aea6b4d3623bab997abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 27 Oct 2022 16:05:29 +0200 Subject: [PATCH 25/62] Fixed: block static variable that might be unused when no logging (travis report) --- srtcore/core.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 048d3f903..42ac4aab9 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9711,8 +9711,7 @@ CUDT::time_point srt::CUDT::getPacketPTS(void*, const CPacket& packet) return m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); } -static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; - +SRT_ATR_UNUSED static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs) { From 0168c5ea583ffd2963bac90e91cbfcb97390f297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Tue, 1 Nov 2022 15:06:33 +0100 Subject: [PATCH 26/62] Fixed after PR comments --- srtcore/core.cpp | 4 ++-- srtcore/core.h | 24 +++++++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 42ac4aab9..6d12656fd 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9706,7 +9706,7 @@ int srt::CUDT::checkLazySpawnLatencyThread() return 0; } -CUDT::time_point srt::CUDT::getPacketPTS(void*, const CPacket& packet) +CUDT::time_point srt::CUDT::getPacketPlayTime(void*, const CPacket& packet) { return m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); } @@ -9728,7 +9728,7 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& const bool retransmitted = pktrexmitflag == 1; time_point pts = steady_clock::now() + milliseconds_from(m_iTsbPdDelay_ms); - IF_HEAVY_LOGGING(pts = getPacketPTS(NULL, rpkt)); + IF_HEAVY_LOGGING(pts = getPacketPlayTime(NULL, rpkt)); int buffer_add_result; bool adding_successful = true; diff --git a/srtcore/core.h b/srtcore/core.h index 93674cb3b..8cbf47650 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1053,11 +1053,29 @@ class CUDT std::pair packData(CPacket& packet); int processData(CUnit* unit); + + /// This function passes the incoming packet to the initial processing + /// (like packet filter) and is about to store it effectively to the + /// receiver buffer and do some postprocessing (decryption) if necessary + /// and report the status thereof. + /// + /// @param incoming [in] The packet coming from the network medium + /// @param w_new_inserted [out] Set false, if the packet already exists, otherwise true (packet added) + /// @param w_was_sent_in_order [out] Set false, if the packet was belated, but had no R flag set. + /// @param w_srt_loss_seqs [out] Gets inserted a loss, if this function has detected it. + /// + /// @return 0 The call was successful (regardless if the packet was accepted or not). + /// @return -1 The call has failed: no space left in the buffer. + /// @return -2 The incoming packet exceeds the expected sequence by more than a length of the buffer (irrepairable discrepancy). int handleSocketPacketReception(const std::vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); - // Group passed here by void* because in the current imp it's - // unused and shall not be used in case when bonding is off - time_point getPacketPTS(void* grp, const CPacket& packet); + // This function is to return the packet's play time (time when + // it is submitted to the reading application) of the given packet. + // This grp passed here by void* because in the current imp it's + // unused and shall not be used in case when ENABLE_BONDING=0. + time_point getPacketPlayTime(void* grp, const CPacket& packet); + + /// Checks and spawns the TSBPD thread if required. int checkLazySpawnLatencyThread(); void processClose(); From 870509f7e2b1a5ff72eb4520912ce25058d8ddf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 3 Nov 2022 12:08:19 +0100 Subject: [PATCH 27/62] Renamed getPacketPTS. Moved call to obtain PTS under belated condition --- srtcore/core.cpp | 7 +++---- srtcore/core.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 6d12656fd..37a13dc94 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9706,7 +9706,7 @@ int srt::CUDT::checkLazySpawnLatencyThread() return 0; } -CUDT::time_point srt::CUDT::getPacketPlayTime(void*, const CPacket& packet) +CUDT::time_point srt::CUDT::getPktTsbPdTime(void*, const CPacket& packet) { return m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); } @@ -9727,9 +9727,6 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& const int pktrexmitflag = m_bPeerRexmitFlag ? (rpkt.getRexmitFlag() ? 1 : 0) : 2; const bool retransmitted = pktrexmitflag == 1; - time_point pts = steady_clock::now() + milliseconds_from(m_iTsbPdDelay_ms); - IF_HEAVY_LOGGING(pts = getPacketPlayTime(NULL, rpkt)); - int buffer_add_result; bool adding_successful = true; @@ -9745,6 +9742,8 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& if (offset < 0) { IF_HEAVY_LOGGING(exc_type = "BELATED"); + time_point pts = getPktTsbPdTime(NULL, rpkt); + enterCS(m_StatsLock); const double bltime = (double) CountIIR( uint64_t(m_stats.traceBelatedTime) * 1000, diff --git a/srtcore/core.h b/srtcore/core.h index 8cbf47650..81979bb83 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1073,7 +1073,7 @@ class CUDT // it is submitted to the reading application) of the given packet. // This grp passed here by void* because in the current imp it's // unused and shall not be used in case when ENABLE_BONDING=0. - time_point getPacketPlayTime(void* grp, const CPacket& packet); + time_point getPktTsbPdTime(void* grp, const CPacket& packet); /// Checks and spawns the TSBPD thread if required. int checkLazySpawnLatencyThread(); From c103433ab32ee3b426c17343e41937bcce261526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 3 Nov 2022 15:53:55 +0100 Subject: [PATCH 28/62] Updated taking pts for stats and logs only where needed. Added more elaborate comment to signaling TSBPD on reception --- srtcore/core.cpp | 42 ++++++++++++++++++++++++++++-------------- srtcore/core.h | 4 ++-- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 88acac6b6..a23f5bb19 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -10211,7 +10211,7 @@ int srt::CUDT::checkLazySpawnLatencyThread() #if ENABLE_HEAVY_LOGGING #if ENABLE_BONDING -CUDT::time_point srt::CUDT::getPacketPTS(CUDTGroup* grp, const CPacket& packet) +CUDT::time_point srt::CUDT::getPktTsbPdTime(CUDTGroup* grp, const CPacket& packet) { steady_clock::time_point pts; @@ -10235,7 +10235,7 @@ CUDT::time_point srt::CUDT::getPacketPTS(CUDTGroup* grp, const CPacket& packet) return pts; } #else -CUDT::time_point srt::CUDT::getPacketPTS(void*, const CPacket& packet) +CUDT::time_point srt::CUDT::getPktTsbPdTime(void*, const CPacket& packet) { return m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); } @@ -10259,9 +10259,6 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& const int pktrexmitflag = m_bPeerRexmitFlag ? (rpkt.getRexmitFlag() ? 1 : 0) : 2; const bool retransmitted = pktrexmitflag == 1; - time_point pts = steady_clock::now() + milliseconds_from(m_iTsbPdDelay_ms); - IF_HEAVY_LOGGING(pts = getPacketPTS(NULL, rpkt)); - int buffer_add_result; bool adding_successful = true; @@ -10277,6 +10274,8 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& if (offset < 0) { IF_HEAVY_LOGGING(exc_type = "BELATED"); + time_point pts = getPktTsbPdTime(NULL, rpkt); + enterCS(m_StatsLock); const double bltime = (double) CountIIR( uint64_t(m_stats.traceBelatedTime) * 1000, @@ -10479,8 +10478,6 @@ bool srt::CUDT::handleGroupPacketReception(CUDTGroup* grp, const vector& { bool excessive SRT_ATR_UNUSED = true; // stays true unless it was successfully added - time_point pts = steady_clock::now() + milliseconds_from(m_iTsbPdDelay_ms); - // Loop over all incoming packets that were filtered out. // In case when there is no filter, there's just one packet in 'incoming', // the one that came in the input of CUDT::processData(). @@ -10492,8 +10489,6 @@ bool srt::CUDT::handleGroupPacketReception(CUDTGroup* grp, const vector& const bool retransmitted = pktrexmitflag == 1; const int pktsz = (int) rpkt.getLength(); - IF_HEAVY_LOGGING(pts = getPacketPTS(grp, rpkt)); - bool adding_successful = true; bool have_loss = false; @@ -10531,6 +10526,8 @@ bool srt::CUDT::handleGroupPacketReception(CUDTGroup* grp, const vector& if (info.result == CRcvBuffer::InsertInfo::BELATED) { + time_point pts = getPktTsbPdTime(grp, rpkt); + IF_HEAVY_LOGGING(exc_type = "BELATED"); enterCS(m_StatsLock); const double bltime = (double) CountIIR( @@ -10690,17 +10687,16 @@ int srt::CUDT::processData(CUnit* in_unit) } - steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) - steady_clock::time_point pts = steady_clock::time_point() + tsbpddelay; - #if ENABLE_HEAVY_LOGGING { // It's easier to remove the latency factor from this value than to add a function // that exposes the details basing on which this value is calculated. + steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) + time_point pts; #if ENABLE_BONDING - pts = getPacketPTS(gkeeper.group, packet); + pts = getPktTsbPdTime(gkeeper.group, packet); #else - pts = getPacketPTS(NULL, packet); + pts = getPktTsbPdTime(NULL, packet); #endif steady_clock::time_point ets = pts - tsbpddelay; @@ -10984,6 +10980,24 @@ int srt::CUDT::processData(CUnit* in_unit) // delivery (after drop), this time we have received a packet to be delivered // earlier than that, so we need to notify TSBPD immediately so that it // updates this itself, not sleep until the previously set time. + + // The meaning of m_bWakeOnRecv: + // - m_bWakeOnRecv is set by TSBPD thread and means that it wishes to be woken up + // on every received packet. Hence we signal always if a new packet was inserted. + // - even if TSBPD doesn't wish to be woken up on every reception (because it sleeps + // until the play time of the next deliverable packet), it will be woken up when + // next_tsbpd_avail is set because it means this time is earlier than the time until + // which TSBPD sleeps, so it must be woken up prematurely. It might be more performant + // to simply update the sleeping end time of TSBPD, but there's no way to do it, so + // we simply wake TSBPD up and count on that it will update its sleeping settings. + + // XXX Consider: as CUniqueSync locks m_RecvLock, it means that the next instruction + // gets run only when TSBPD falls asleep again. Might be a good idea to record the + // TSBPD end sleeping time - as an alternative to m_bWakeOnRecv - and after locking + // a mutex check this time again and compare it against next_tsbpd_avail; might be + // that if this difference is smaller than "dirac" (could be hard to reliably compare + // this time, unless it's set from this very value), there's no need to wake the TSBPD + // thread because it will wake up on time requirement at the right time anyway. if (m_bTsbPd && ((m_bWakeOnRecv && new_inserted) || next_tsbpd_avail != time_point())) { HLOGC(qrlog.Debug, log << "processData: will SIGNAL TSBPD for socket. WakeOnRecv=" << m_bWakeOnRecv diff --git a/srtcore/core.h b/srtcore/core.h index 8217900b8..d97c954d7 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1083,9 +1083,9 @@ class CUDT #endif #if ENABLE_HEAVY_LOGGING #if ENABLE_BONDING - time_point getPacketPTS(CUDTGroup* grp, const CPacket& packet); + time_point getPktTsbPdTime(CUDTGroup* grp, const CPacket& packet); #else - time_point getPacketPTS(void* grp, const CPacket& packet); + time_point getPktTsbPdTime(void* grp, const CPacket& packet); #endif #endif From b3f768f4e39dd0d6796f932c092c99df80945975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 3 Nov 2022 17:16:56 +0100 Subject: [PATCH 29/62] Improved logging for the receiver side --- srtcore/core.cpp | 6 ++++++ srtcore/group.cpp | 6 +++--- srtcore/queue.cpp | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index a23f5bb19..7629603ea 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -5275,6 +5275,12 @@ void * srt::CUDT::tsbpd(void* param) const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); tsNextDelivery = info.tsbpd_time; + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: packet check: %" + << info.seqno << " T=" << FormatTime(tsNextDelivery) + << " diff-now-playtime=" << FormatDuration(tnow - tsNextDelivery) + << " ready=" << is_time_to_deliver + << " ondrop=" << info.seq_gap); + if (!self->m_bTLPktDrop) { rxready = !info.seq_gap && is_time_to_deliver; diff --git a/srtcore/group.cpp b/srtcore/group.cpp index e53e36463..4378b4a70 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -5956,9 +5956,9 @@ void* CUDTGroup::tsbpd(void* param) steady_clock::time_point tsNextDelivery = info.tsbpd_time; bool rxready = false; - HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: packet check: T=" - << FormatTime(tsNextDelivery) << " diff-in-late=" - << FormatDuration(tnow - tsNextDelivery) + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: packet check: %" + << info.seqno << " T=" << FormatTime(tsNextDelivery) + << " diff-now-playtime=" << FormatDuration(tnow - tsNextDelivery) << " ready=" << is_time_to_deliver << " ondrop=" << info.seq_gap); diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 11fd041fe..4c8cfc0bc 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -1341,6 +1341,7 @@ srt::EReadStatus srt::CRcvQueue::worker_RetrieveUnit(int32_t& w_id, CUnit*& w_un w_id = w_unit->m_Packet.m_iID; HLOGC(qrlog.Debug, log << "INCOMING PACKET: FROM=" << w_addr.str() << " BOUND=" << m_pChannel->bindAddressAny().str() << " " + << "NOW=" << FormatTime(sync::steady_clock::now()) << " " << w_unit->m_Packet.Info()); } return rst; From b15876eec4428b3244e3f076c9d4c93544132918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 7 Nov 2022 14:35:39 +0100 Subject: [PATCH 30/62] Added some comments regarding the new TSBPD triggering rules --- srtcore/core.cpp | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 2bd5aabd3..e92977824 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -7944,10 +7944,12 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) #endif IF_HEAVY_LOGGING(int32_t oldack = m_iRcvLastSkipAck); - // If TSBPD is enabled, then INSTEAD OF signaling m_RecvDataCond, - // signal m_RcvTsbPdCond. This will kick in the tsbpd thread, which - // will signal m_RecvDataCond when there's time to play for particular - // data packet. + // Signalling m_RecvDataCond is not dane when TSBPD is on. + // This signalling is done in file mode in order to keep the + // API reader thread sleeping until there is a "bigger portion" + // of data to read. In TSBPD mode this isn't done because every + // packet has its individual delivery time and its readiness is signed + // off by the TSBPD thread. HLOGC(xtlog.Debug, log << CONID() << "ACK: clip %" << oldack << "-%" << ack << ", REVOKED " << CSeqNo::seqoff(ack, m_iRcvLastAck) << " from RCV buffer"); @@ -8982,11 +8984,31 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) } // When the drop request was received, it means that there are // packets for which there will never be ACK sent; if the TSBPD thread - // is currently in the ACK-waiting state, it may never exit. + // is currently in the recv-waiting state, it may never exit. if (m_bTsbPd) { - HLOGP(inlog.Debug, "DROPREQ: signal TSBPD"); - rcvtscc.notify_one(); + // XXX Likely this is not necessary because: + // 1. In the recv-waiting state, that is, when TSBPD thread + // sleeps forever, it will be woken up anyway on packet + // reception. + // 2. If there are any packets in the buffer and the initial + // packet cell is empty (in which situation any drop could + // occur), TSBPD thread is sleeping timely, until the playtime + // of the first "drop up to" packet. Dropping changes nothing here. + // 3. If the buffer is empty, there's nothing "to drop up to", so + // this function will not change anything in the buffer and so + // in the reception state as well. + // 4. If the TSBPD thread is waiting until a play-ready packet is + // retrieved by the API call (in which case it is also sleeping + // forever until it's woken up by the API call), it may remain + // stalled forever, if the application isn't reading the play-ready + // packet. But this means that the application got stalled anyway. + // TSBPD when woken up could at best state that there's still a + // play-ready packet that is still not retrieved and fall back + // to sleep (forever). + + //HLOGP(inlog.Debug, "DROPREQ: signal TSBPD"); + //rcvtscc.notify_one(); } } @@ -11050,6 +11072,8 @@ int srt::CUDT::processData(CUnit* in_unit) HLOGC(qrlog.Debug, log << CONID() << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs)); sendLossReport(filter_loss_seqs); + // XXX unsure as to whether this should change anything in the TSBPD conditions. + // Might be that this trigger is not necessary. if (m_bTsbPd) { HLOGC(qrlog.Debug, log << CONID() << "loss: signaling TSBPD cond"); From 652e35da0e7a9702496f7ef9fa95049b9e13c5e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 7 Nov 2022 16:56:33 +0100 Subject: [PATCH 31/62] Applied changes for improved TSBPD and receiver buffer --- srtcore/buffer_rcv.cpp | 637 ++++++++++++++++++++++++++++++++------- srtcore/buffer_rcv.h | 301 +++++++++++++++--- srtcore/core.cpp | 264 +++++++++------- srtcore/core.h | 6 +- test/test_buffer_rcv.cpp | 5 +- 5 files changed, 952 insertions(+), 261 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 7bfb00ad8..ede2033cb 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -98,7 +98,7 @@ namespace { * RcvBufferNew (circular buffer): * * |<------------------- m_iSize ----------------------------->| - * | |<----------- m_iMaxPosInc ------------>| | + * | |<----------- m_iMaxPosOff ------------>| | * | | | | * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ * | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] @@ -112,20 +112,22 @@ namespace { * thread safety: * m_iStartPos: CUDT::m_RecvLock * m_iLastAckPos: CUDT::m_AckLock - * m_iMaxPosInc: none? (modified on add and ack + * m_iMaxPosOff: none? (modified on add and ack */ CRcvBuffer::CRcvBuffer(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool bMessageAPI) : m_entries(size) , m_szSize(size) // TODO: maybe just use m_entries.size() , m_pUnitQueue(unitqueue) - , m_iStartSeqNo(initSeqNo) + , m_iStartSeqNo(initSeqNo) // NOTE: SRT_SEQNO_NONE is allowed here. , m_iStartPos(0) + , m_iEndPos(0) + , m_iDropPos(0) , m_iFirstNonreadPos(0) - , m_iMaxPosInc(0) + , m_iMaxPosOff(0) , m_iNotch(0) - , m_numOutOfOrderPackets(0) - , m_iFirstReadableOutOfOrder(-1) + , m_numRandomPackets(0) + , m_iFirstRandomMsgPos(-1) , m_bPeerRexmitFlag(true) , m_bMessageAPI(bMessageAPI) , m_iBytesCount(0) @@ -137,7 +139,7 @@ CRcvBuffer::CRcvBuffer(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool b CRcvBuffer::~CRcvBuffer() { - // Can be optimized by only iterating m_iMaxPosInc from m_iStartPos. + // Can be optimized by only iterating m_iMaxPosOff from m_iStartPos. for (FixedArray::iterator it = m_entries.begin(); it != m_entries.end(); ++it) { if (!it->pUnit) @@ -148,7 +150,13 @@ CRcvBuffer::~CRcvBuffer() } } -int CRcvBuffer::insert(CUnit* unit) +void CRcvBuffer::debugShowState(const char* source SRT_ATR_UNUSED) +{ + HLOGC(brlog.Debug, log << "RCV-BUF-STATE(" << source << ") start=" << m_iStartPos << " end=" << m_iEndPos + << " drop=" << m_iDropPos << " max-off=+" << m_iMaxPosOff << " seq[start]=%" << m_iStartSeqNo); +} + +CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) { SRT_ASSERT(unit != NULL); const int32_t seqno = unit->m_Packet.getSeqNo(); @@ -159,53 +167,264 @@ int CRcvBuffer::insert(CUnit* unit) IF_RCVBUF_DEBUG(scoped_log.ss << " msgno " << unit->m_Packet.getMsgSeq(m_bPeerRexmitFlag)); IF_RCVBUF_DEBUG(scoped_log.ss << " m_iStartSeqNo " << m_iStartSeqNo << " offset " << offset); + int32_t avail_seq; + int avail_range; + if (offset < 0) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -2"); - return -2; + return InsertInfo(InsertInfo::BELATED); } + IF_HEAVY_LOGGING(string debug_source = "insert %" + Sprint(seqno)); if (offset >= (int)capacity()) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -3"); - return -3; + + // Calculation done for the sake of possible discrepancy + // in order to inform the caller what to do. + if (m_entries[m_iStartPos].status == EntryState_Avail) + { + avail_seq = packetAt(m_iStartPos).getSeqNo(); + avail_range = m_iEndPos - m_iStartPos; + } + else if (m_iDropPos == m_iEndPos) + { + avail_seq = SRT_SEQNO_NONE; + avail_range = 0; + } + else + { + avail_seq = packetAt(m_iDropPos).getSeqNo(); + + // We don't know how many packets follow it exactly, + // but in this case it doesn't matter. We know that + // at least one is there. + avail_range = 1; + } + + IF_HEAVY_LOGGING(debugShowState((debug_source + " overflow").c_str())); + + return InsertInfo(InsertInfo::DISCREPANCY, avail_seq, avail_range); } // 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); - const int pos = (m_iStartPos + offset) % m_szSize; - if (offset >= m_iMaxPosInc) - m_iMaxPosInc = offset + 1; + const int newpktpos = incPos(m_iStartPos, offset); + const int prev_max_off = m_iMaxPosOff; + bool extended_end = false; + if (offset >= m_iMaxPosOff) + { + m_iMaxPosOff = offset + 1; + extended_end = true; + } // Packet already exists - SRT_ASSERT(pos >= 0 && pos < int(m_szSize)); - if (m_entries[pos].status != EntryState_Empty) + // (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). + SRT_ASSERT(newpktpos >= 0 && newpktpos < int(m_szSize)); + if (m_entries[newpktpos].status != EntryState_Empty) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -1"); - return -1; + IF_HEAVY_LOGGING(debugShowState((debug_source + " redundant").c_str())); + return InsertInfo(InsertInfo::REDUNDANT); } - SRT_ASSERT(m_entries[pos].pUnit == NULL); + SRT_ASSERT(m_entries[newpktpos].pUnit == NULL); m_pUnitQueue->makeUnitTaken(unit); - m_entries[pos].pUnit = unit; - m_entries[pos].status = EntryState_Avail; + m_entries[newpktpos].pUnit = unit; + m_entries[newpktpos].status = EntryState_Avail; countBytes(1, (int)unit->m_Packet.getLength()); + // 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; + + int prev_max_pos = incPos(m_iStartPos, prev_max_off); + + // Update flags + // Case [A] + if (extended_end) + { + // THIS means that the buffer WAS CONTIGUOUS BEFORE. + if (m_iEndPos == prev_max_pos) + { + // THIS means that the new packet didn't CAUSE a gap + if (m_iMaxPosOff == prev_max_off + 1) + { + // This means that m_iEndPos now shifts by 1, + // and m_iDropPos must be shifted together with it, + // as there's no drop to point. + m_iEndPos = incPos(m_iStartPos, m_iMaxPosOff); + m_iDropPos = m_iEndPos; + } + else + { + // Otherwise we have a drop-after-gap candidate + // which is the currently inserted packet. + // Therefore m_iEndPos STAYS WHERE IT IS. + m_iDropPos = incPos(m_iStartPos, m_iMaxPosOff - 1); + } + } + } + // + // Since this place, every newpktpos is in the range + // between m_iEndPos (inclusive) and a position for m_iMaxPosOff. + + // Here you can use prev_max_pos as the position represented + // by m_iMaxPosOff, as if !extended_end, it was unchanged. + else if (newpktpos == m_iEndPos) + { + // Case [D]: inserted a packet at the first gap following the + // contiguous region. This makes a potential to extend the + // contiguous region and we need to find its end. + + // If insertion happened at the very first packet, it is the + // new earliest packet now. In any other situation under this + // condition there's some contiguous packet range preceding + // this position. + if (m_iEndPos == m_iStartPos) + { + earlier_time = getPktTsbPdTime(unit->m_Packet.getMsgTimeStamp()); + } + + updateGapInfo(prev_max_pos); + } + // XXX Not sure if that's the best performant comparison + // What is meant here is that newpktpos is between + // m_iEndPos and m_iDropPos, though we know it's after m_iEndPos. + // CONSIDER: make m_iDropPos rather m_iDropOff, this will make + // this comparison a simple subtraction. Note that offset will + // have to be updated on every shift of m_iStartPos. + else if (cmpPos(newpktpos, m_iDropPos) < 0) + { + // Case [C]: the newly inserted packet precedes the + // previous earliest delivery position after drop, + // that is, there is now a "better" after-drop delivery + // candidate. + + // New position updated a valid packet on an earlier + // position than the drop position was before, although still + // following a gap. + // + // We know it because if the position has filled a gap following + // a valid packet, this preceding valid packet would be pointed + // by m_iDropPos, or it would point to some earlier packet in a + // contiguous series of valid packets following a gap, hence + // the above condition wouldn't be satisfied. + m_iDropPos = newpktpos; + + // If there's an inserted packet BEFORE drop-pos (which makes it + // a new drop-pos), while the very first packet is absent (the + // below condition), it means we have a new earliest-available + // packet. Otherwise we would have only a newly updated drop + // position, but still following some earlier contiguous range + // of valid packets - so it's earlier than previous drop, but + // not earlier than the earliest packet. + if (m_iStartPos == m_iEndPos) + { + earlier_time = getPktTsbPdTime(unit->m_Packet.getMsgTimeStamp()); + } + } + // OTHERWISE: case [D] in which nothing is to be updated. + // If packet "in order" flag is zero, it can be read out of order. // With TSBPD enabled packets are always assumed in order (the flag is ignored). if (!m_tsbpd.isEnabled() && m_bMessageAPI && !unit->m_Packet.getMsgOrderFlag()) { - ++m_numOutOfOrderPackets; - onInsertNotInOrderPacket(pos); + ++m_numRandomPackets; + onInsertNotInOrderPacket(newpktpos); } updateNonreadPos(); + + CPacket* avail_packet = NULL; + + if (m_entries[m_iStartPos].pUnit && m_entries[m_iStartPos].status == EntryState_Avail) + { + avail_packet = &packetAt(m_iStartPos); + avail_range = m_iEndPos - m_iStartPos; + } + else if (!m_tsbpd.isEnabled() && m_iFirstRandomMsgPos != -1) + { + // In case when TSBPD is off, we take into account the message mode + // where messages may potentially span for multiple packets, therefore + // the only "next deliverable" is the first complete message that satisfies + // the order requirement. + avail_packet = &packetAt(m_iFirstRandomMsgPos); + avail_range = 1; + } + else if (m_iDropPos != m_iEndPos) + { + avail_packet = &packetAt(m_iDropPos); + avail_range = 1; + } + IF_RCVBUF_DEBUG(scoped_log.ss << " returns 0 (OK)"); - return 0; + IF_HEAVY_LOGGING(debugShowState((debug_source + " ok").c_str())); + + if (avail_packet) + return InsertInfo(InsertInfo::INSERTED, avail_packet->getSeqNo(), avail_range, earlier_time); + else + return InsertInfo(InsertInfo::INSERTED); // No packet candidate (NOTE: impossible in live mode) } +// This function should be called after having m_iEndPos +// has somehow be set to position of a non-empty cell. +// This can happen by two reasons: +// - the cell has been filled by incoming packet +// - the value has been reset due to shifted m_iStartPos +// This means that you have to search for a new gap and +// update the m_iEndPos and m_iDropPos fields, or set them +// both to the end of range. +// +// prev_max_pos should be the position represented by m_iMaxPosOff. +// Passed because it is already calculated in insert(), otherwise +// it would have to be calculated here again. +void CRcvBuffer::updateGapInfo(int prev_max_pos) +{ + int pos = m_iEndPos; + + // First, search for the next gap, max until m_iMaxPosOff. + for ( ; pos != prev_max_pos; pos = incPos(pos)) + { + if (m_entries[pos].status == EntryState_Empty) + { + break; + } + } + if (pos == prev_max_pos) + { + // Reached the end and found no gaps. + m_iEndPos = prev_max_pos; + m_iDropPos = prev_max_pos; + } + else + { + // Found a gap at pos + m_iEndPos = pos; + m_iDropPos = pos; // fallback, although SHOULD be impossible + // So, search for the first position to drop up to. + for ( ; pos != prev_max_pos; pos = incPos(pos)) + { + if (m_entries[pos].status != EntryState_Empty) + { + m_iDropPos = pos; + break; + } + } + } +} + +/// Request to remove from the receiver buffer +/// all packets with earlier sequence than @a seqno. +/// (Meaning, the packet with given sequence shall +/// be the first packet in the buffer after the operation). int CRcvBuffer::dropUpTo(int32_t seqno) { IF_RCVBUF_DEBUG(ScopedLog scoped_log); @@ -218,9 +437,9 @@ int CRcvBuffer::dropUpTo(int32_t seqno) return 0; } - m_iMaxPosInc -= len; - if (m_iMaxPosInc < 0) - m_iMaxPosInc = 0; + m_iMaxPosOff -= len; + if (m_iMaxPosOff < 0) + m_iMaxPosOff = 0; const int iDropCnt = len; while (len > 0) @@ -235,13 +454,21 @@ int CRcvBuffer::dropUpTo(int32_t seqno) // Update positions m_iStartSeqNo = seqno; // Move forward if there are "read/drop" entries. + // (This call MAY shift m_iStartSeqNo further.) releaseNextFillerEntries(); + + // Start from here and search fort the next gap + m_iEndPos = m_iDropPos = m_iStartPos; + updateGapInfo(incPos(m_iStartPos, m_iMaxPosOff)); + // Set nonread position to the starting position before updating, // because start position was increased, and preceeding packets are invalid. m_iFirstNonreadPos = m_iStartPos; updateNonreadPos(); if (!m_tsbpd.isEnabled() && m_bMessageAPI) - updateFirstReadableOutOfOrder(); + updateFirstReadableRandom(); + + IF_HEAVY_LOGGING(debugShowState(("drop %" + Sprint(seqno)).c_str())); return iDropCnt; } @@ -250,7 +477,7 @@ int CRcvBuffer::dropAll() if (empty()) return 0; - const int end_seqno = CSeqNo::incseq(m_iStartSeqNo, m_iMaxPosInc); + const int end_seqno = CSeqNo::incseq(m_iStartSeqNo, m_iMaxPosOff); return dropUpTo(end_seqno); } @@ -259,7 +486,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) IF_RCVBUF_DEBUG(ScopedLog scoped_log); IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::dropMessage: seqnolo " << seqnolo << " seqnohi " << seqnohi << " m_iStartSeqNo " << m_iStartSeqNo); // TODO: count bytes as removed? - const int end_pos = incPos(m_iStartPos, m_iMaxPosInc); + const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); if (msgno > 0) // including SRT_MSGNO_NONE and SRT_MSGNO_CONTROL { IF_RCVBUF_DEBUG(scoped_log.ss << " msgno " << msgno); @@ -286,6 +513,11 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) // Check if units before m_iFirstNonreadPos are dropped. bool needUpdateNonreadPos = (minDroppedOffset != -1 && minDroppedOffset <= getRcvDataSize()); releaseNextFillerEntries(); + + // Start from here and search fort the next gap + m_iEndPos = m_iDropPos = m_iStartSeqNo; + updateGapInfo(end_pos); + if (needUpdateNonreadPos) { m_iFirstNonreadPos = m_iStartPos; @@ -293,10 +525,11 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) } if (!m_tsbpd.isEnabled() && m_bMessageAPI) { - if (!checkFirstReadableOutOfOrder()) - m_iFirstReadableOutOfOrder = -1; - updateFirstReadableOutOfOrder(); + if (!checkFirstReadableRandom()) + m_iFirstRandomMsgPos = -1; + updateFirstReadableRandom(); } + IF_HEAVY_LOGGING(debugShowState(("dropmsg off %" + Sprint(seqnolo)).c_str())); return iDropCnt; } @@ -341,24 +574,47 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) } if (!m_tsbpd.isEnabled() && m_bMessageAPI) { - if (!checkFirstReadableOutOfOrder()) - m_iFirstReadableOutOfOrder = -1; - updateFirstReadableOutOfOrder(); + if (!checkFirstReadableRandom()) + m_iFirstRandomMsgPos = -1; + updateFirstReadableRandom(); } + IF_HEAVY_LOGGING(debugShowState(("dropmsg off %" + Sprint(seqnolo)).c_str())); return iDropCnt; } -int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) +bool CRcvBuffer::getContiguousEnd(int32_t& w_seq) const +{ + if (m_iStartPos == m_iEndPos) + { + // Initial contiguous region empty (including empty buffer). + HLOGC(rbuflog.Debug, log << "CONTIG: empty, give up base=%" << m_iStartSeqNo); + w_seq = m_iStartSeqNo; + return m_iMaxPosOff > 0; + } + + int end_off = offPos(m_iStartPos, m_iEndPos); + + w_seq = CSeqNo::incseq(m_iStartSeqNo, end_off); + + HLOGC(rbuflog.Debug, log << "CONTIG: endD=" << end_off << " maxD=" << m_iMaxPosOff << " base=%" << m_iStartSeqNo + << " end=%" << w_seq); + + return (end_off < m_iMaxPosOff); +} + +int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair* pw_seqrange) { const bool canReadInOrder = hasReadableInorderPkts(); - if (!canReadInOrder && m_iFirstReadableOutOfOrder < 0) + if (!canReadInOrder && m_iFirstRandomMsgPos < 0) { LOGC(rbuflog.Warn, log << "CRcvBuffer.readMessage(): nothing to read. Ignored isRcvDataReady() result?"); return 0; } - const int readPos = canReadInOrder ? m_iStartPos : m_iFirstReadableOutOfOrder; + //const bool canReadInOrder = m_iFirstNonreadPos != m_iStartPos; + const int readPos = canReadInOrder ? m_iStartPos : m_iFirstRandomMsgPos; + const bool isReadingFromStart = (readPos == m_iStartPos); // Indicates if the m_iStartPos can be changed IF_RCVBUF_DEBUG(ScopedLog scoped_log); IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::readMessage. m_iStartSeqNo " << m_iStartSeqNo << " m_iStartPos " << m_iStartPos << " readPos " << readPos); @@ -367,7 +623,10 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) char* dst = data; int pkts_read = 0; int bytes_extracted = 0; // The total number of bytes extracted from the buffer. - const bool updateStartPos = (readPos == m_iStartPos); // Indicates if the m_iStartPos can be changed + + int32_t out_seqlo = SRT_SEQNO_NONE; + int32_t out_seqhi = SRT_SEQNO_NONE; + for (int i = readPos;; i = incPos(i)) { SRT_ASSERT(m_entries[i].pUnit); @@ -381,6 +640,11 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) const size_t pktsize = packet.getLength(); const int32_t pktseqno = packet.getSeqNo(); + if (out_seqlo == SRT_SEQNO_NONE) + out_seqlo = pktseqno; + + out_seqhi = pktseqno; + // unitsize can be zero const size_t unitsize = std::min(remain, pktsize); memcpy(dst, packet.m_pcData, unitsize); @@ -393,8 +657,8 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) if (m_tsbpd.isEnabled()) updateTsbPdTimeBase(packet.getMsgTimeStamp()); - if (m_numOutOfOrderPackets && !packet.getMsgOrderFlag()) - --m_numOutOfOrderPackets; + if (m_numRandomPackets && !packet.getMsgOrderFlag()) + --m_numRandomPackets; const bool pbLast = packet.getMsgBoundary() & PB_LAST; if (msgctrl && (packet.getMsgBoundary() & PB_FIRST)) @@ -409,11 +673,26 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) msgctrl->pktseq = pktseqno; releaseUnitInPos(i); - if (updateStartPos) + if (isReadingFromStart) { m_iStartPos = incPos(i); - --m_iMaxPosInc; - SRT_ASSERT(m_iMaxPosInc >= 0); + --m_iMaxPosOff; + + // m_iEndPos and m_iDropPos should be + // equal to m_iStartPos only if the buffer + // is empty - but in this case the extraction will + // not be done. Otherwise m_iEndPos should + // point to the first empty cell, and m_iDropPos + // point to the first busy cell after a gap, or + // at worst be equal to m_iEndPos. + + // Therefore none of them should be updated + // because they should be constantly updated + // on an incoming packet, while this function + // should not read further than to the first + // empty cell at worst. + + SRT_ASSERT(m_iMaxPosOff >= 0); m_iStartSeqNo = CSeqNo::incseq(pktseqno); } else @@ -424,8 +703,8 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) if (pbLast) { - if (readPos == m_iFirstReadableOutOfOrder) - m_iFirstReadableOutOfOrder = -1; + if (readPos == m_iFirstRandomMsgPos) + m_iFirstRandomMsgPos = -1; break; } } @@ -434,16 +713,59 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) releaseNextFillerEntries(); - if (!isInRange(m_iStartPos, m_iMaxPosInc, m_szSize, m_iFirstNonreadPos)) + if (!isInRange(m_iStartPos, m_iMaxPosOff, m_szSize, m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; //updateNonreadPos(); } + // Now that we have m_iStartPos potentially shifted, reinitialize + // m_iEndPos and m_iDropPos. + + int pend_pos = incPos(m_iStartPos, m_iMaxPosOff); + + // First check: is anything in the beginning + if (m_entries[m_iStartPos].status == EntryState_Avail) + { + // If so, shift m_iEndPos up to the first nonexistent unit + // XXX Try to optimize search by splitting into two loops if necessary. + + m_iEndPos = incPos(m_iStartPos); + while (m_entries[m_iEndPos].status == EntryState_Avail) + { + m_iEndPos = incPos(m_iEndPos); + if (m_iEndPos == pend_pos) + break; + } + + // If we had first packet available, then there's also no drop pos. + m_iDropPos = m_iEndPos; + + } + else + { + // If not, reset m_iEndPos and search for the first after-drop candidate. + m_iEndPos = m_iStartPos; + m_iDropPos = m_iEndPos; + + while (m_entries[m_iDropPos].status != EntryState_Avail) + { + m_iDropPos = incPos(m_iDropPos); + if (m_iDropPos == pend_pos) + { + // Nothing found - set drop pos equal to end pos, + // which means there's no drop + m_iDropPos = m_iEndPos; + break; + } + } + } + + if (!m_tsbpd.isEnabled()) - // We need updateFirstReadableOutOfOrder() here even if we are reading inorder, + // We need updateFirstReadableRandom() here even if we are reading inorder, // incase readable inorder packets are all read out. - updateFirstReadableOutOfOrder(); + updateFirstReadableRandom(); const int bytes_read = int(dst - data); if (bytes_read < bytes_extracted) @@ -453,6 +775,10 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) IF_RCVBUF_DEBUG(scoped_log.ss << " pldi64 " << *reinterpret_cast(data)); + if (pw_seqrange) + *pw_seqrange = make_pair(out_seqlo, out_seqhi); + + IF_HEAVY_LOGGING(debugShowState("readmsg")); return bytes_read; } @@ -530,8 +856,8 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) m_iNotch = 0; m_iStartPos = p; - --m_iMaxPosInc; - SRT_ASSERT(m_iMaxPosInc >= 0); + --m_iMaxPosOff; + SRT_ASSERT(m_iMaxPosOff >= 0); m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); } else @@ -547,7 +873,7 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) // Update positions // Set nonread position to the starting position before updating, // because start position was increased, and preceeding packets are invalid. - if (!isInRange(m_iStartPos, m_iMaxPosInc, m_szSize, m_iFirstNonreadPos)) + if (!isInRange(m_iStartPos, m_iMaxPosOff, m_szSize, m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; } @@ -557,6 +883,7 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) LOGC(rbuflog.Error, log << "readBufferTo: 0 bytes read. m_iStartPos=" << m_iStartPos << ", m_iFirstNonreadPos=" << m_iFirstNonreadPos); } + IF_HEAVY_LOGGING(debugShowState("readbuf")); return iBytesRead; } @@ -572,15 +899,12 @@ int CRcvBuffer::readBufferToFile(fstream& ofs, int len) bool CRcvBuffer::hasAvailablePackets() const { - return hasReadableInorderPkts() || (m_numOutOfOrderPackets > 0 && m_iFirstReadableOutOfOrder != -1); + return hasReadableInorderPkts() || (m_numRandomPackets > 0 && m_iFirstRandomMsgPos != -1); } int CRcvBuffer::getRcvDataSize() const { - if (m_iFirstNonreadPos >= m_iStartPos) - return m_iFirstNonreadPos - m_iStartPos; - - return int(m_szSize + m_iFirstNonreadPos - m_iStartPos); + return offPos(m_iStartPos, m_iFirstNonreadPos); } int CRcvBuffer::getTimespan_ms() const @@ -588,10 +912,10 @@ int CRcvBuffer::getTimespan_ms() const if (!m_tsbpd.isEnabled()) return 0; - if (m_iMaxPosInc == 0) + if (m_iMaxPosOff == 0) return 0; - const int lastpos = incPos(m_iStartPos, m_iMaxPosInc - 1); + const int lastpos = incPos(m_iStartPos, m_iMaxPosOff - 1); // Should not happen if TSBPD is enabled (reading out of order is not allowed). SRT_ASSERT(m_entries[lastpos].pUnit != NULL); if (m_entries[lastpos].pUnit == NULL) @@ -631,35 +955,28 @@ int CRcvBuffer::getRcvDataSize(int& bytes, int& timespan) const CRcvBuffer::PacketInfo CRcvBuffer::getFirstValidPacketInfo() const { - const int end_pos = incPos(m_iStartPos, m_iMaxPosInc); - for (int i = m_iStartPos; i != end_pos; i = incPos(i)) + // Check the state of the very first packet first + if (m_entries[m_iStartPos].status == EntryState_Avail) { - // TODO: Maybe check status? - if (!m_entries[i].pUnit) - continue; - - const CPacket& packet = m_entries[i].pUnit->m_Packet; - const PacketInfo info = { packet.getSeqNo(), i != m_iStartPos, getPktTsbPdTime(packet.getMsgTimeStamp()) }; - return info; + SRT_ASSERT(m_entries[m_iStartPos].pUnit); + return (PacketInfo) { m_iStartSeqNo, false /*no gap*/, getPktTsbPdTime(packetAt(m_iStartPos).getMsgTimeStamp()) }; + } + // If not, get the information from the drop + if (m_iDropPos != m_iEndPos) + { + const CPacket& pkt = packetAt(m_iDropPos); + return (PacketInfo) { pkt.getSeqNo(), true, getPktTsbPdTime(pkt.getMsgTimeStamp()) }; } - const PacketInfo info = { -1, false, time_point() }; - return info; + return (PacketInfo) { SRT_SEQNO_NONE, false, time_point() }; } std::pair CRcvBuffer::getAvailablePacketsRange() const { - const int seqno_last = CSeqNo::incseq(m_iStartSeqNo, (int) countReadable()); + const int seqno_last = CSeqNo::incseq(m_iStartSeqNo, offPos(m_iStartPos, m_iFirstNonreadPos)); return std::pair(m_iStartSeqNo, seqno_last); } -size_t CRcvBuffer::countReadable() const -{ - if (m_iFirstNonreadPos >= m_iStartPos) - return m_iFirstNonreadPos - m_iStartPos; - return m_szSize + m_iFirstNonreadPos - m_iStartPos; -} - bool CRcvBuffer::isRcvDataReady(time_point time_now) const { const bool haveInorderPackets = hasReadableInorderPkts(); @@ -668,8 +985,8 @@ bool CRcvBuffer::isRcvDataReady(time_point time_now) const if (haveInorderPackets) return true; - SRT_ASSERT((!m_bMessageAPI && m_numOutOfOrderPackets == 0) || m_bMessageAPI); - return (m_numOutOfOrderPackets > 0 && m_iFirstReadableOutOfOrder != -1); + SRT_ASSERT((!m_bMessageAPI && m_numRandomPackets == 0) || m_bMessageAPI); + return (m_numRandomPackets > 0 && m_iFirstRandomMsgPos != -1); } if (!haveInorderPackets) @@ -693,11 +1010,11 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstReadablePacketInfo(time_point time_no const PacketInfo info = {packet.getSeqNo(), false, time_point()}; return info; } - SRT_ASSERT((!m_bMessageAPI && m_numOutOfOrderPackets == 0) || m_bMessageAPI); - if (m_iFirstReadableOutOfOrder >= 0) + SRT_ASSERT((!m_bMessageAPI && m_numRandomPackets == 0) || m_bMessageAPI); + if (m_iFirstRandomMsgPos >= 0) { - SRT_ASSERT(m_numOutOfOrderPackets > 0); - const CPacket& packet = m_entries[m_iFirstReadableOutOfOrder].pUnit->m_Packet; + SRT_ASSERT(m_numRandomPackets > 0); + const CPacket& packet = m_entries[m_iFirstRandomMsgPos].pUnit->m_Packet; const PacketInfo info = {packet.getSeqNo(), true, time_point()}; return info; } @@ -742,9 +1059,9 @@ bool CRcvBuffer::dropUnitInPos(int pos) } else if (m_bMessageAPI && !m_entries[pos].pUnit->m_Packet.getMsgOrderFlag()) { - --m_numOutOfOrderPackets; - if (pos == m_iFirstReadableOutOfOrder) - m_iFirstReadableOutOfOrder = -1; + --m_numRandomPackets; + if (pos == m_iFirstRandomMsgPos) + m_iFirstRandomMsgPos = -1; } releaseUnitInPos(pos); return true; @@ -759,24 +1076,24 @@ void CRcvBuffer::releaseNextFillerEntries() releaseUnitInPos(pos); pos = incPos(pos); m_iStartPos = pos; - --m_iMaxPosInc; - if (m_iMaxPosInc < 0) - m_iMaxPosInc = 0; + --m_iMaxPosOff; + if (m_iMaxPosOff < 0) + m_iMaxPosOff = 0; } } // TODO: Is this function complete? There are some comments left inside. void CRcvBuffer::updateNonreadPos() { - if (m_iMaxPosInc == 0) + if (m_iMaxPosOff == 0) return; - const int end_pos = incPos(m_iStartPos, m_iMaxPosInc); // The empty position right after the last valid entry. + const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); // The empty position right after the last valid entry. int pos = m_iFirstNonreadPos; while (m_entries[pos].pUnit && m_entries[pos].status == EntryState_Avail) { - if (m_bMessageAPI && (m_entries[pos].pUnit->m_Packet.getMsgBoundary() & PB_FIRST) == 0) + if (m_bMessageAPI && (packetAt(pos).getMsgBoundary() & PB_FIRST) == 0) break; for (int i = pos; i != end_pos; i = incPos(i)) @@ -786,8 +1103,12 @@ void CRcvBuffer::updateNonreadPos() break; } + // m_iFirstNonreadPos is moved to the first position BEHIND + // the PB_LAST packet of the message. There's no guaratnee that + // the cell at this position isn't empty. + // Check PB_LAST only in message mode. - if (!m_bMessageAPI || m_entries[i].pUnit->m_Packet.getMsgBoundary() & PB_LAST) + if (!m_bMessageAPI || packetAt(i).getMsgBoundary() & PB_LAST) { m_iFirstNonreadPos = incPos(i); break; @@ -818,7 +1139,7 @@ int CRcvBuffer::findLastMessagePkt() void CRcvBuffer::onInsertNotInOrderPacket(int insertPos) { - if (m_numOutOfOrderPackets == 0) + if (m_numRandomPackets == 0) return; // If the following condition is true, there is already a packet, @@ -827,20 +1148,20 @@ void CRcvBuffer::onInsertNotInOrderPacket(int insertPos) // // There might happen that the packet being added precedes the previously found one. // However, it is allowed to re bead out of order, so no need to update the position. - if (m_iFirstReadableOutOfOrder >= 0) + if (m_iFirstRandomMsgPos >= 0) return; // Just a sanity check. This function is called when a new packet is added. // So the should be unacknowledged packets. - SRT_ASSERT(m_iMaxPosInc > 0); + SRT_ASSERT(m_iMaxPosOff > 0); SRT_ASSERT(m_entries[insertPos].pUnit); - const CPacket& pkt = m_entries[insertPos].pUnit->m_Packet; + const CPacket& pkt = packetAt(insertPos); const PacketBoundary boundary = pkt.getMsgBoundary(); //if ((boundary & PB_FIRST) && (boundary & PB_LAST)) //{ // // This packet can be read out of order - // m_iFirstReadableOutOfOrder = insertPos; + // m_iFirstRandomMsgPos = insertPos; // return; //} @@ -856,18 +1177,18 @@ void CRcvBuffer::onInsertNotInOrderPacket(int insertPos) if (firstPktPos < 0) return; - m_iFirstReadableOutOfOrder = firstPktPos; + m_iFirstRandomMsgPos = firstPktPos; return; } -bool CRcvBuffer::checkFirstReadableOutOfOrder() +bool CRcvBuffer::checkFirstReadableRandom() { - if (m_numOutOfOrderPackets <= 0 || m_iFirstReadableOutOfOrder < 0 || m_iMaxPosInc == 0) + if (m_numRandomPackets <= 0 || m_iFirstRandomMsgPos < 0 || m_iMaxPosOff == 0) return false; - const int endPos = incPos(m_iStartPos, m_iMaxPosInc); + const int endPos = incPos(m_iStartPos, m_iMaxPosOff); int msgno = -1; - for (int pos = m_iFirstReadableOutOfOrder; pos != endPos; pos = incPos(pos)) + for (int pos = m_iFirstRandomMsgPos; pos != endPos; pos = incPos(pos)) { if (!m_entries[pos].pUnit) return false; @@ -888,20 +1209,20 @@ bool CRcvBuffer::checkFirstReadableOutOfOrder() return false; } -void CRcvBuffer::updateFirstReadableOutOfOrder() +void CRcvBuffer::updateFirstReadableRandom() { - if (hasReadableInorderPkts() || m_numOutOfOrderPackets <= 0 || m_iFirstReadableOutOfOrder >= 0) + if (hasReadableInorderPkts() || m_numRandomPackets <= 0 || m_iFirstRandomMsgPos >= 0) return; - if (m_iMaxPosInc == 0) + if (m_iMaxPosOff == 0) return; // TODO: unused variable outOfOrderPktsRemain? - int outOfOrderPktsRemain = (int) m_numOutOfOrderPackets; + int outOfOrderPktsRemain = (int) m_numRandomPackets; // Search further packets to the right. // First check if there are packets to the right. - const int lastPos = (m_iStartPos + m_iMaxPosInc - 1) % m_szSize; + const int lastPos = (m_iStartPos + m_iMaxPosOff - 1) % m_szSize; int posFirst = -1; int posLast = -1; @@ -940,7 +1261,7 @@ void CRcvBuffer::updateFirstReadableOutOfOrder() if (boundary & PB_LAST) { - m_iFirstReadableOutOfOrder = posFirst; + m_iFirstRandomMsgPos = posFirst; return; } @@ -955,7 +1276,7 @@ int CRcvBuffer::scanNotInOrderMessageRight(const int startPos, int msgNo) const { // Search further packets to the right. // First check if there are packets to the right. - const int lastPos = (m_iStartPos + m_iMaxPosInc - 1) % m_szSize; + const int lastPos = (m_iStartPos + m_iMaxPosOff - 1) % m_szSize; if (startPos == lastPos) return -1; @@ -997,7 +1318,7 @@ int CRcvBuffer::scanNotInOrderMessageLeft(const int startPos, int msgNo) const if (!m_entries[pos].pUnit) return -1; - const CPacket& pkt = m_entries[pos].pUnit->m_Packet; + const CPacket& pkt = packetAt(pos); if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { @@ -1055,19 +1376,19 @@ string CRcvBuffer::strFullnessState(bool enable_debug_log, int iFirstUnackSeqNo, if (enable_debug_log) { ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo - << " m_iStartPos=" << m_iStartPos << " m_iMaxPosInc=" << m_iMaxPosInc << ". "; + << " m_iStartPos=" << m_iStartPos << " m_iMaxPosInc=" << m_iMaxPosOff << ". "; } ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << m_szSize << " pkts. "; - if (m_tsbpd.isEnabled() && m_iMaxPosInc > 0) + if (m_tsbpd.isEnabled() && m_iMaxPosOff > 0) { const PacketInfo nextValidPkt = getFirstValidPacketInfo(); ss << "(TSBPD ready in "; if (!is_zero(nextValidPkt.tsbpd_time)) { ss << count_milliseconds(nextValidPkt.tsbpd_time - tsNow) << "ms"; - const int iLastPos = incPos(m_iStartPos, m_iMaxPosInc - 1); + const int iLastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); if (m_entries[iLastPos].pUnit) { ss << ", timespan "; @@ -1116,4 +1437,92 @@ void CRcvBuffer::updRcvAvgDataSize(const steady_clock::time_point& now) m_mavg.update(now, pkts, bytes, timespan_ms); } +int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) +{ + int offset = CSeqNo::seqoff(m_iStartSeqNo, fromseq); + + // Check if it's still inside the buffer + if (offset < 0 || offset >= m_iMaxPosOff) + { + HLOGC(rbuflog.Debug, log << "getFirstLossSeq: offset=" << offset << " for %" << fromseq + << " (with max=" << m_iMaxPosOff << ") - NO LOSS FOUND"); + return SRT_SEQNO_NONE; + } + + // Start position + int pos = incPos(m_iStartPos, offset); + + // Ok; likely we should stand at the m_iEndPos position. + // If this given position is earlier than this, then + // m_iEnd stands on the first loss, unless it's equal + // to the position pointed by m_iMaxPosOff. + + int32_t ret_seq = SRT_SEQNO_NONE; + int ret_off = m_iMaxPosOff; + + int end_off = offPos(m_iStartPos, m_iEndPos); + if (pos < end_off) + { + // If m_iEndPos has such a value, then there are + // no loss packets at all. + if (end_off != m_iMaxPosOff) + { + ret_seq = CSeqNo::incseq(m_iStartSeqNo, end_off); + ret_off = end_off; + } + } + else + { + // Could be strange, but just as the caller wishes: + // find the first loss since this point on + // You can't rely on m_iEndPos, you are beyond that now. + // So simply find the next hole. + + // REUSE offset as a control variable + for (; offset < m_iMaxPosOff; ++offset) + { + int pos = incPos(m_iStartPos, offset); + if (m_entries[pos].status == EntryState_Empty) + { + ret_off = offset; + ret_seq = CSeqNo::incseq(m_iStartSeqNo, offset); + break; + } + } + } + + // If found no loss, just return this value and do not + // rewrite nor look for anything. + + // Also no need to search anything if only the beginning was + // being looked for. + if (ret_seq == SRT_SEQNO_NONE || !pw_end) + return ret_seq; + + // We want also the end range, so continue from where you + // stopped. + + // Start from ret_off + 1 because we know already that ret_off + // points to an empty cell. + for (int off = ret_off + 1; off < m_iMaxPosOff; ++off) + { + int pos = incPos(m_iStartPos, off); + if (m_entries[pos].status != EntryState_Empty) + { + *pw_end = CSeqNo::incseq(m_iStartSeqNo, off - 1); + return ret_seq; + } + } + + // Fallback - this should be impossible, so issue a log. + LOGC(rbuflog.Error, log << "IPE: empty cell pos=" << pos << " %" << CSeqNo::incseq(m_iStartSeqNo, ret_off) << " not followed by any valid cell"); + + // Return this in the last resort - this could only be a situation when + // a packet has somehow disappeared, but it contains empty cells up to the + // end of buffer occupied range. This shouldn't be possible at all because + // there must be a valid packet at least at the last occupied cell. + return SRT_SEQNO_NONE; +} + + } // namespace srt diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 52e927f22..1cdce3ad8 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -20,28 +20,193 @@ namespace srt { -/* - * Circular receiver buffer. - * - * |<------------------- m_szSize ---------------------------->| - * | |<------------ m_iMaxPosInc ----------->| | - * | | | | - * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ - * | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] - * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ - * | | - * | \__last pkt received - * | - * \___ m_iStartPos: first message to read - * - * m_pUnit[i]->status_: 0: free, 1: good, 2: read, 3: dropped (can be combined with read?) - * - * thread safety: - * start_pos_: CUDT::m_RecvLock - * first_unack_pos_: CUDT::m_AckLock - * max_pos_inc_: none? (modified on add and ack - * first_nonread_pos_: - */ +// +// Circular receiver buffer. +// +// |<------------------- m_szSize ---------------------------->| +// | |<------------ m_iMaxPosOff ----------->| | +// | | | | +// +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ +// | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] +// +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ +// | | | | +// | | | \__last pkt received +// | | | +// | | \___ m_iDropPos +// | | +// | \___ m_iEndPos +// | +// \___ m_iStartPos: first packet position in the buffer +// +// m_pUnit[i]->status_: 0: free, 1: good, 2: read, 3: dropped (can be combined with read?) +// +// thread safety: +// start_pos_: CUDT::m_RecvLock +// first_unack_pos_: CUDT::m_AckLock +// max_pos_inc_: none? (modified on add and ack +// first_nonread_pos_: +// +// +// m_iStartPos: the first packet that should be read (might be empty) +// m_iEndPos: the end of contiguous range. Empty if m_iEndPos == m_iStartPos +// m_iDropPos: a packet available for retrieval after a drop. If == m_iEndPos, no such packet. +// +// Operational rules: +// +// Initially: +// m_iStartPos = 0 +// m_iEndPos = 0 +// m_iDropPos = 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_iEndPos shifted by 1 +// m_iDropPos = m_iEndPos +// +// 2. Position: after a loss, newest. +// +// m_iStartPos unchanged. +// m_iEndPos unchanged. +// m_iDropPos: +// - if it was == m_iEndPos, set to this +// - 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) | +// +// 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. +// class CRcvBuffer { @@ -54,6 +219,32 @@ class CRcvBuffer ~CRcvBuffer(); public: + + void debugShowState(const char* source); + + struct InsertInfo + { + enum Result { INSERTED = 0, REDUNDANT = -1, BELATED = -2, DISCREPANCY = -3 } result; + + // Below fields are valid only if result == INSERTED. Otherwise they have trap repro. + + int first_seq; // sequence of the first available readable packet + time_point first_time; // Time of the new, earlier packet that appeared ready, or null-time if this didn't change. + int avail_range; + + InsertInfo(Result r, int fp_seq = SRT_SEQNO_NONE, int range = 0, + time_point fp_time = time_point()) + : result(r), first_seq(fp_seq), first_time(fp_time), avail_range(range) + { + } + + InsertInfo() + : result(REDUNDANT), first_seq(SRT_SEQNO_NONE), avail_range(0) + { + } + + }; + /// Insert a unit into the buffer. /// Similar to CRcvBuffer::addData(CUnit* unit, int offset) /// @@ -63,7 +254,8 @@ class CRcvBuffer /// @return 0 on success, -1 if packet is already in buffer, -2 if packet is before m_iStartSeqNo. /// -3 if a packet is offset is ahead the buffer capacity. // TODO: Previously '-2' also meant 'already acknowledged'. Check usage of this value. - int insert(CUnit* unit); + InsertInfo insert(CUnit* unit); + void updateGapInfo(int prev_max_pos); /// Drop packets in the receiver buffer from the current position up to the seqno (excluding seqno). /// @param [in] seqno drop units up to this sequence number @@ -84,6 +276,17 @@ class CRcvBuffer /// @return the number of packets actually dropped. int dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno); + /// Extract the "expected next" packet sequence. + /// Extract the past-the-end sequence for the first packet + /// that is expected to arrive next with preserving the packet order. + /// If the buffer is empty or the very first cell is lacking a packet, + /// it returns the sequence assigned to the first cell. Otherwise it + /// returns the sequence representing the first empty cell (the next + /// cell to the last received packet, if there are no loss-holes). + /// @param [out] w_seq: returns the sequence (always valid) + /// @return true if this sequence is followed by any valid packets + bool getContiguousEnd(int32_t& w_seq) const; + /// Read the whole message from one or several packets. /// /// @param [in,out] data buffer to write the message into. @@ -93,7 +296,7 @@ class CRcvBuffer /// @return actual number of bytes extracted from the buffer. /// 0 if nothing to read. /// -1 on failure. - int readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl = NULL); + int readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl = NULL, std::pair* pw_seqrange = NULL); /// Read acknowledged data into a user buffer. /// @param [in, out] dst pointer to the target user buffer. @@ -179,11 +382,11 @@ class CRcvBuffer /// @note CSeqNo::seqoff(first, second) is 0 if nothing to read. std::pair getAvailablePacketsRange() const; - size_t countReadable() const; + int32_t getFirstLossSeq(int32_t fromseq, int32_t* opt_end = NULL); bool empty() const { - return (m_iMaxPosInc == 0); + return (m_iMaxPosOff == 0); } /// Return buffer capacity. @@ -195,6 +398,14 @@ class CRcvBuffer return m_szSize - 1; } + /// 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; + } + int64_t getDrift() const { return m_tsbpd.drift(); } // TODO: make thread safe? @@ -225,6 +436,18 @@ class CRcvBuffer inline int incPos(int pos, int inc = 1) const { return (pos + inc) % m_szSize; } inline int decPos(int pos) const { return (pos - 1) >= 0 ? (pos - 1) : int(m_szSize - 1); } inline int offPos(int pos1, int pos2) const { return (pos2 >= pos1) ? (pos2 - pos1) : int(m_szSize + pos2 - pos1); } + inline int cmpPos(int pos2, int pos1) const + { + // XXX maybe not the best implementation, but this keeps up to the rule + int off1 = pos1 >= m_iStartPos ? pos1 - m_iStartPos : pos1 + m_szSize - m_iStartPos; + int off2 = pos2 >= m_iStartPos ? pos2 - m_iStartPos : pos2 + m_szSize - m_iStartPos; + + return off2 - off1; + } + + // NOTE: Assumes that pUnit != NULL + CPacket& packetAt(int pos) { return m_entries[pos].pUnit->m_Packet; } + const CPacket& packetAt(int pos) const { return m_entries[pos].pUnit->m_Packet; } private: void countBytes(int pkts, int bytes); @@ -247,9 +470,9 @@ class CRcvBuffer /// Scan for availability of out of order packets. void onInsertNotInOrderPacket(int insertpos); - // Check if m_iFirstReadableOutOfOrder is still readable. - bool checkFirstReadableOutOfOrder(); - void updateFirstReadableOutOfOrder(); + // Check if m_iFirstRandomMsgPos is still readable. + bool checkFirstReadableRandom(); + void updateFirstReadableRandom(); int scanNotInOrderMessageRight(int startPos, int msgNo) const; int scanNotInOrderMessageLeft(int startPos, int msgNo) const; @@ -303,20 +526,26 @@ class CRcvBuffer //static Entry emptyEntry() { return Entry { NULL, EntryState_Empty }; } - FixedArray m_entries; + 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 int m_iStartSeqNo; int m_iStartPos; // the head position for I/O (inclusive) + int m_iEndPos; // past-the-end of the contiguous region since m_iStartPos + int m_iDropPos; // points past m_iEndPos to the first deliverable after a gap, or == m_iEndPos if no such packet int m_iFirstNonreadPos; // First position that can't be read (<= m_iLastAckPos) - int m_iMaxPosInc; // the furthest data position - int m_iNotch; // the starting read point of the first unit + int m_iMaxPosOff; // the furthest data position + int m_iNotch; // index of the first byte to read in the first ready-to-read packet (used in file/stream mode) + + size_t m_numRandomPackets; // The number of stored packets with "inorder" flag set to false - size_t m_numOutOfOrderPackets; // The number of stored packets with "inorder" flag set to false - int m_iFirstReadableOutOfOrder; // In case of out ouf order packet, points to a position of the first such packet to - // read + /// Points to the first packet of a message that has out-of-order flag + /// and is complete (all packets from first to last are in the buffer). + /// If there is no such message in the buffer, it contains -1. + int m_iFirstRandomMsgPos; bool m_bPeerRexmitFlag; // Needed to read message number correctly const bool m_bMessageAPI; // Operation mode flag: message or stream. @@ -342,6 +571,8 @@ class CRcvBuffer time_point getTsbPdTimeBase(uint32_t usPktTimestamp) const; void updateTsbPdTimeBase(uint32_t usPktTimestamp); + bool isTsbPd() const { return m_tsbpd.isEnabled(); } + /// Form a string of the current buffer fullness state. /// number of packets acknowledged, TSBPD readiness, etc. std::string strFullnessState(bool enable_debug_log, int iFirstUnackSeqNo, const time_point& tsNow) const; diff --git a/srtcore/core.cpp b/srtcore/core.cpp index c614844dd..68824dd8e 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -292,7 +292,7 @@ void srt::CUDT::construct() m_bPeerTsbPd = false; m_iPeerTsbPdDelay_ms = 0; m_bTsbPd = false; - m_bTsbPdAckWakeup = false; + m_bWakeOnRecv = false; m_bGroupTsbPd = false; m_bPeerTLPktDrop = false; @@ -5254,7 +5254,7 @@ void * srt::CUDT::tsbpd(void* param) CUniqueSync recvdata_lcc (self->m_RecvLock, self->m_RecvDataCond); CSync tsbpd_cc(self->m_RcvTsbPdCond, recvdata_lcc.locker()); - self->m_bTsbPdAckWakeup = true; + self->m_bWakeOnRecv = true; while (!self->m_bClosing) { steady_clock::time_point tsNextDelivery; // Next packet delivery time @@ -5272,6 +5272,12 @@ void * srt::CUDT::tsbpd(void* param) const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); tsNextDelivery = info.tsbpd_time; + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: packet check: %" + << info.seqno << " T=" << FormatTime(tsNextDelivery) + << " diff-now-playtime=" << FormatDuration(tnow - tsNextDelivery) + << " ready=" << is_time_to_deliver + << " ondrop=" << info.seq_gap); + if (!self->m_bTLPktDrop) { rxready = !info.seq_gap && is_time_to_deliver; @@ -5374,7 +5380,7 @@ void * srt::CUDT::tsbpd(void* param) * Buffer at head of queue is not ready to play. * Schedule wakeup when it will be. */ - self->m_bTsbPdAckWakeup = false; + self->m_bWakeOnRecv = false; HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << info.seqno << " T=" << FormatTime(tsNextDelivery) << " - waiting " << count_milliseconds(timediff) << "ms"); @@ -5396,7 +5402,7 @@ void * srt::CUDT::tsbpd(void* param) * - Closing the connection */ HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: no data, scheduling wakeup at ack"); - self->m_bTsbPdAckWakeup = true; + self->m_bWakeOnRecv = true; THREAD_PAUSED(); tsbpd_cc.wait(); THREAD_RESUMED(); @@ -7782,10 +7788,28 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp m_tsLastSndTime.store(steady_clock::now()); } +bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) +{ + if (!m_pRcvBuffer) + { + LOGP(cnlog.Error, "IPE: ack can't be sent, buffer doesn't exist and no group membership"); + return false; + } + { + ScopedLock buflock (m_RcvBufferLock); + bool has_followers = m_pRcvBuffer->getContiguousEnd((w_seq)); + if (has_followers) + w_log_reason = "first lost"; + else + w_log_reason = "expected next"; + } + + return true; +} + int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { SRT_ASSERT(ctrlpkt.getMsgTimeStamp() != 0); - int32_t ack; // First unacknowledged packet seqnuence number (acknowledge up to ack). int nbsent = 0; int local_prevack = 0; @@ -7800,39 +7824,17 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) (void)l_saveback; // kill compiler warning: unused variable `l_saveback` [-Wunused-variable] local_prevack = m_iDebugPrevLastAck; - - string reason = "first lost"; // just for "a reason" of giving particular % for ACK #endif + string reason; // just for "a reason" of giving particular % for ACK -#if ENABLE_BONDING - dropToGroupRecvBase(); -#endif - - // The TSBPD thread may change the first lost sequence record (TLPKTDROP). - // To avoid it the m_RcvBufferLock has to be acquired. - UniqueLock bufflock(m_RcvBufferLock); - - { - // If there is no loss, the ACK is the current largest sequence number plus 1; - // Otherwise it is the smallest sequence number in the receiver loss list. - ScopedLock lock(m_RcvLossLock); - // TODO: Consider the Fresh Loss list as well!!! - ack = m_pRcvLossList->getFirstLostSeq(); - } - - // We don't need to check the length prematurely, - // if length is 0, this will return SRT_SEQNO_NONE. - // If so happened, simply use the latest received pkt + 1. - if (ack == SRT_SEQNO_NONE) - { - ack = CSeqNo::incseq(m_iRcvCurrSeqNo); - IF_HEAVY_LOGGING(reason = "expected next"); - } + int32_t ack; // First unacknowledged packet sequence number (acknowledge up to ack). + if (!getFirstNoncontSequence((ack), (reason))) + return nbsent; if (m_iRcvLastAckAck == ack) { HLOGC(xtlog.Debug, - log << CONID() << "sendCtrl(UMSG_ACK): last ACK %" << ack << "(" << reason << ") == last ACKACK"); + log << CONID() << "sendCtrl(UMSG_ACK): last ACK %" << ack << "(" << reason << ") == last ACKACK"); return nbsent; } @@ -7840,7 +7842,6 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) // to save time on buffer processing and bandwidth/AS measurement, a lite ACK only feeds back an ACK number if (size == SEND_LITE_ACK) { - bufflock.unlock(); ctrlpkt.pack(UMSG_ACK, NULL, &ack, size); ctrlpkt.m_iID = m_PeerID; nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); @@ -7848,6 +7849,16 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) return nbsent; } + // Lock the group existence until this function ends. This will be useful + // also on other places. +#if ENABLE_BONDING + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); +#endif + + // There are new received packets to acknowledge, update related information. + /* tsbpd thread may also call ackData when skipping packet so protect code */ + UniqueLock bufflock(m_RcvBufferLock); + // IF ack %> m_iRcvLastAck // There are new received packets to acknowledge, update related information. if (CSeqNo::seqcmp(ack, m_iRcvLastAck) > 0) @@ -7894,21 +7905,32 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) #endif IF_HEAVY_LOGGING(int32_t oldack = m_iRcvLastSkipAck); - // If TSBPD is enabled, then INSTEAD OF signaling m_RecvDataCond, - // signal m_RcvTsbPdCond. This will kick in the tsbpd thread, which - // will signal m_RecvDataCond when there's time to play for particular - // data packet. + // Signalling m_RecvDataCond is not dane when TSBPD is on. + // This signalling is done in file mode in order to keep the + // API reader thread sleeping until there is a "bigger portion" + // of data to read. In TSBPD mode this isn't done because every + // packet has its individual delivery time and its readiness is signed + // off by the TSBPD thread. HLOGC(xtlog.Debug, log << CONID() << "ACK: clip %" << oldack << "-%" << ack << ", REVOKED " << CSeqNo::seqoff(ack, m_iRcvLastAck) << " from RCV buffer"); if (m_bTsbPd) { - /* Newly acknowledged data, signal TsbPD thread */ + /* + There's no need to update TSBPD in the wake-on-recv state + from ACK because it is being done already in the receiver thread + when a newly inserted packet caused provision of a new candidate + that could be delivered soon. Also, this flag is only used in TSBPD + mode and can be only set to true in the TSBPD thread. + + // Newly acknowledged data, signal TsbPD thread // CUniqueSync tslcc (m_RecvLock, m_RcvTsbPdCond); - // m_bTsbPdAckWakeup is protected by m_RecvLock in the tsbpd() thread - if (m_bTsbPdAckWakeup) + // m_bWakeOnRecv is protected by m_RecvLock in the tsbpd() thread + if (m_bWakeOnRecv) tslcc.notify_one(); + + */ } else { @@ -7970,7 +7992,8 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) else { // Not possible (m_iRcvCurrSeqNo+1 <% m_iRcvLastAck ?) - LOGC(xtlog.Error, log << CONID() << "sendCtrl(UMSG_ACK): IPE: curr %" << ack << " <% last %" << m_iRcvLastAck); + LOGC(xtlog.Error, log << CONID()<< "sendCtrl(UMSG_ACK): IPE: curr(" << reason << ") %" << ack + << " <% last %" << m_iRcvLastAck); return nbsent; } @@ -8763,8 +8786,28 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) // is currently in the ACK-waiting state, it may never exit. if (m_bTsbPd) { - HLOGP(inlog.Debug, "DROPREQ: signal TSBPD"); - rcvtscc.notify_one(); + // XXX Likely this is not necessary because: + // 1. In the recv-waiting state, that is, when TSBPD thread + // sleeps forever, it will be woken up anyway on packet + // reception. + // 2. If there are any packets in the buffer and the initial + // packet cell is empty (in which situation any drop could + // occur), TSBPD thread is sleeping timely, until the playtime + // of the first "drop up to" packet. Dropping changes nothing here. + // 3. If the buffer is empty, there's nothing "to drop up to", so + // this function will not change anything in the buffer and so + // in the reception state as well. + // 4. If the TSBPD thread is waiting until a play-ready packet is + // retrieved by the API call (in which case it is also sleeping + // forever until it's woken up by the API call), it may remain + // stalled forever, if the application isn't reading the play-ready + // packet. But this means that the application got stalled anyway. + // TSBPD when woken up could at best state that there's still a + // play-ready packet that is still not retrieved and fall back + // to sleep (forever). + + //HLOGP(inlog.Debug, "DROPREQ: signal TSBPD"); + //rcvtscc.notify_one(); } } @@ -9701,14 +9744,14 @@ CUDT::time_point srt::CUDT::getPktTsbPdTime(void*, const CPacket& packet) SRT_ATR_UNUSED static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; -int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs) +int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& w_new_inserted, steady_clock::time_point& w_next_tsbpd, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs) { bool excessive SRT_ATR_UNUSED = true; // stays true unless it was successfully added // Loop over all incoming packets that were filtered out. // In case when there is no filter, there's just one packet in 'incoming', // the one that came in the input of this function. - for (vector::const_iterator unitIt = incoming.begin(); unitIt != incoming.end(); ++unitIt) + for (vector::const_iterator unitIt = incoming.begin(); unitIt != incoming.end() && !m_bBroken; ++unitIt) { CUnit * u = *unitIt; CPacket &rpkt = u->m_Packet; @@ -9787,7 +9830,26 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& } } - buffer_add_result = m_pRcvBuffer->insert(u); + CRcvBuffer::InsertInfo info = m_pRcvBuffer->insert(u); + + // Remember this value in order to CHECK if there's a need + // to request triggering TSBPD in case when TSBPD is in the + // state of waiting forever and wants to know if there's any + // possible time to wake up known earlier than that. + + // Note that in case of the "builtin group reader" (its own + // buffer), there's no need to do it here because it has also + // its own TSBPD thread. + + if (info.result == CRcvBuffer::InsertInfo::INSERTED) + { + // This may happen multiple times in the loop, so update only if earlier. + if (w_next_tsbpd == time_point() || w_next_tsbpd > info.first_time) + w_next_tsbpd = info.first_time; + w_new_inserted = true; + } + buffer_add_result = int(info.result); + if (buffer_add_result < 0) { // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. @@ -9797,8 +9859,6 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& } else { - w_new_inserted = true; - IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); excessive = false; if (u->m_Packet.getMsgCryptoFlags() != EK_NOENC) @@ -10107,6 +10167,7 @@ int srt::CUDT::processData(CUnit* in_unit) int res = handleSocketPacketReception(incoming, (new_inserted), + (next_tsbpd_avail), (was_sent_in_order), (srt_loss_seqs)); @@ -10181,6 +10242,42 @@ int srt::CUDT::processData(CUnit* in_unit) return -1; } + // 1. This is set to true in case when TSBPD during the last check + // has seen no packet candidate to ever deliver, hence it needs + // an update on that. Note that this is also false if TSBPD thread + // isn't running. + // 2. If next_tsbpd_avail is set, it means that in the buffer there is + // a new packet that precedes the previously earliest available packet. + // This means that if TSBPD was sleeping up to the time of this earliest + // delivery (after drop), this time we have received a packet to be delivered + // earlier than that, so we need to notify TSBPD immediately so that it + // updates this itself, not sleep until the previously set time. + + // The meaning of m_bWakeOnRecv: + // - m_bWakeOnRecv is set by TSBPD thread and means that it wishes to be woken up + // on every received packet. Hence we signal always if a new packet was inserted. + // - even if TSBPD doesn't wish to be woken up on every reception (because it sleeps + // until the play time of the next deliverable packet), it will be woken up when + // next_tsbpd_avail is set because it means this time is earlier than the time until + // which TSBPD sleeps, so it must be woken up prematurely. It might be more performant + // to simply update the sleeping end time of TSBPD, but there's no way to do it, so + // we simply wake TSBPD up and count on that it will update its sleeping settings. + + // XXX Consider: as CUniqueSync locks m_RecvLock, it means that the next instruction + // gets run only when TSBPD falls asleep again. Might be a good idea to record the + // TSBPD end sleeping time - as an alternative to m_bWakeOnRecv - and after locking + // a mutex check this time again and compare it against next_tsbpd_avail; might be + // that if this difference is smaller than "dirac" (could be hard to reliably compare + // this time, unless it's set from this very value), there's no need to wake the TSBPD + // thread because it will wake up on time requirement at the right time anyway. + if (m_bTsbPd && ((m_bWakeOnRecv && new_inserted) || next_tsbpd_avail != time_point())) + { + HLOGC(qrlog.Debug, log << "processData: will SIGNAL TSBPD for socket. WakeOnRecv=" << m_bWakeOnRecv + << " new_inserted=" << new_inserted << " next_tsbpd_avail=" << FormatTime(next_tsbpd_avail)); + CUniqueSync tsbpd_cc(m_RecvLock, m_RcvTsbPdCond); + tsbpd_cc.notify_all(); + } + if (incoming.empty()) { // Treat as excessive. This is when a filter cumulates packets @@ -10197,6 +10294,10 @@ int srt::CUDT::processData(CUnit* in_unit) sendLossReport(srt_loss_seqs); } + // This should not be required with the new receiver buffer because + // signalling happens on every packet reception, if it has changed the + // earliest packet position. + /* if (m_bTsbPd) { HLOGC(qrlog.Debug, log << CONID() << "loss: signaling TSBPD cond"); @@ -10206,6 +10307,7 @@ int srt::CUDT::processData(CUnit* in_unit) { HLOGC(qrlog.Debug, log << CONID() << "loss: socket is not TSBPD, not signaling"); } + */ } // Separately report loss records of those reported by a filter. @@ -10218,6 +10320,8 @@ int srt::CUDT::processData(CUnit* in_unit) HLOGC(qrlog.Debug, log << CONID() << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs)); sendLossReport(filter_loss_seqs); + // XXX unsure as to whether this should change anything in the TSBPD conditions. + // Might be that this trigger is not necessary. if (m_bTsbPd) { HLOGC(qrlog.Debug, log << CONID() << "loss: signaling TSBPD cond"); @@ -10357,67 +10461,6 @@ void srt::CUDT::updateIdleLinkFrom(CUDT* source) setInitialRcvSeq(source->m_iRcvLastSkipAck); } -// XXX This function is currently unused. It should be fixed and put into use. -// See the blocked call in CUDT::processData(). -// XXX REVIEW LOCKS WHEN REACTIVATING! -srt::CUDT::loss_seqs_t srt::CUDT::defaultPacketArrival(void* vself, CPacket& pkt) -{ -// [[using affinity(m_pRcvBuffer->workerThread())]]; - CUDT* self = (CUDT*)vself; - loss_seqs_t output; - - // XXX When an alternative packet arrival callback is installed - // in case of groups, move this part to the groupwise version. - - if (self->m_parent->m_GroupOf) - { - groups::SocketData* gi = self->m_parent->m_GroupMemberData; - if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely - { - HLOGC(qrlog.Debug, log << "defaultPacketArrival: IN-GROUP rcv state transition to RUNNING. NOT checking for loss"); - gi->rcvstate = SRT_GST_RUNNING; - return output; - } - } - - const int initial_loss_ttl = (self->m_bPeerRexmitFlag) ? self->m_iReorderTolerance : 0; - - int seqdiff = CSeqNo::seqcmp(pkt.m_iSeqNo, self->m_iRcvCurrSeqNo); - - HLOGC(qrlog.Debug, log << "defaultPacketArrival: checking sequence " << pkt.m_iSeqNo - << " against latest " << self->m_iRcvCurrSeqNo << " (distance: " << seqdiff << ")"); - - // Loss detection. - if (seqdiff > 1) // packet is later than the very subsequent packet - { - const int32_t seqlo = CSeqNo::incseq(self->m_iRcvCurrSeqNo); - const int32_t seqhi = CSeqNo::decseq(pkt.m_iSeqNo); - - { - // If loss found, insert them to the receiver loss list - ScopedLock lg (self->m_RcvLossLock); - self->m_pRcvLossList->insert(seqlo, seqhi); - - if (initial_loss_ttl) - { - // pack loss list for (possibly belated) NAK - // The LOSSREPORT will be sent in a while. - self->m_FreshLoss.push_back(CRcvFreshLoss(seqlo, seqhi, initial_loss_ttl)); - HLOGF(qrlog.Debug, "defaultPacketArrival: added loss sequence %d-%d (%d) with tolerance %d", seqlo, seqhi, - 1+CSeqNo::seqcmp(seqhi, seqlo), initial_loss_ttl); - } - } - - if (!initial_loss_ttl) - { - // old code; run immediately when tolerance = 0 - // or this feature isn't used because of the peer - output.push_back(make_pair(seqlo, seqhi)); - } - } - - return output; -} #endif /// This function is called when a packet has arrived, which was behind the current @@ -11089,7 +11132,10 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) * not knowing what to retransmit when the only NAK sent by receiver is lost, * all packets past last ACK are retransmitted (rexmitMethod() == SRM_FASTREXMIT). */ + enterCS(m_RcvLossLock); const int loss_len = m_pRcvLossList->getLossLength(); + leaveCS(m_RcvLossLock); + SRT_ASSERT(loss_len >= 0); int debug_decision = BECAUSE_NO_REASON; diff --git a/srtcore/core.h b/srtcore/core.h index 81979bb83..5bb66d436 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -661,6 +661,7 @@ class CUDT /// the receiver fresh loss list. void unlose(const CPacket& oldpacket); void dropFromLossLists(int32_t from, int32_t to); + bool getFirstNoncontSequence(int32_t& w_seq, std::string& w_log_reason); void checkSndTimers(Whether2RegenKm regen = DONT_REGEN_KM); void handshakeDone() @@ -925,7 +926,7 @@ class CUDT sync::CThread m_RcvTsbPdThread; // Rcv TsbPD Thread handle sync::Condition m_RcvTsbPdCond; // TSBPD signals if reading is ready. Use together with m_RecvLock - bool m_bTsbPdAckWakeup; // Signal TsbPd thread on Ack sent + sync::atomic m_bWakeOnRecv; // Expected to be woken up when received a packet sync::Mutex m_RcvTsbPdStartupLock; // Protects TSBPD thread creating and joining CallbackHolder m_cbAcceptHook; @@ -1061,13 +1062,14 @@ class CUDT /// /// @param incoming [in] The packet coming from the network medium /// @param w_new_inserted [out] Set false, if the packet already exists, otherwise true (packet added) + /// @param w_next_tsbpd [out] Get the TSBPD time of the earliest playable packet after insertion /// @param w_was_sent_in_order [out] Set false, if the packet was belated, but had no R flag set. /// @param w_srt_loss_seqs [out] Gets inserted a loss, if this function has detected it. /// /// @return 0 The call was successful (regardless if the packet was accepted or not). /// @return -1 The call has failed: no space left in the buffer. /// @return -2 The incoming packet exceeds the expected sequence by more than a length of the buffer (irrepairable discrepancy). - int handleSocketPacketReception(const std::vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); + int handleSocketPacketReception(const std::vector& incoming, bool& w_new_inserted, sync::steady_clock::time_point& w_next_tsbpd, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); // This function is to return the packet's play time (time when // it is submitted to the reading application) of the given packet. diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp index 4b0d9c833..7d70d6d92 100644 --- a/test/test_buffer_rcv.cpp +++ b/test/test_buffer_rcv.cpp @@ -72,7 +72,10 @@ class CRcvBufferReadMsg EXPECT_TRUE(packet.getMsgOrderFlag()); } - return m_rcv_buffer->insert(unit); + auto info = m_rcv_buffer->insert(unit); + // XXX extra checks? + + return int(info.result); } /// @returns 0 on success, the result of rcv_buffer::insert(..) once it failed From 36c1f674f39a1c4b116c0496ed873936a8a78ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Tue, 8 Nov 2022 17:24:59 +0100 Subject: [PATCH 32/62] Updated usage of shortcuts and new names --- srtcore/buffer_rcv.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index ede2033cb..3c0ce3e5c 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -75,15 +75,15 @@ namespace { #define IF_RCVBUF_DEBUG(instr) (void)0 - // Check if iFirstNonreadPos is in range [iStartPos, (iStartPos + iMaxPosInc) % iSize]. + // 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. - bool isInRange(int iStartPos, int iMaxPosInc, size_t iSize, int iFirstNonreadPos) + bool isInRange(int iStartPos, int iMaxPosOff, size_t iSize, int iFirstNonreadPos) { if (iFirstNonreadPos == iStartPos) return true; - const int iLastPos = (iStartPos + iMaxPosInc) % iSize; + const int iLastPos = (iStartPos + iMaxPosOff) % iSize; const bool isOverrun = iLastPos < iStartPos; if (isOverrun) @@ -499,7 +499,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) continue; // TODO: Break the loop if a massege has been found. No need to search further. - const int32_t msgseq = m_entries[i].pUnit->m_Packet.getMsgSeq(m_bPeerRexmitFlag); + const int32_t msgseq = packetAt(i).getMsgSeq(m_bPeerRexmitFlag); if (msgseq == msgno) { ++iDropCnt; @@ -551,7 +551,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) { // Don't drop messages, if all its packets are already in the buffer. // TODO: Don't drop a several-packet message if all packets are in the buffer. - if (m_entries[i].pUnit && m_entries[i].pUnit->m_Packet.getMsgBoundary() == PB_SOLO) + if (m_entries[i].pUnit && packetAt(i).getMsgBoundary() == PB_SOLO) continue; dropUnitInPos(i); @@ -636,7 +636,7 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pairm_Packet; + const CPacket& packet = packetAt(i); const size_t pktsize = packet.getLength(); const int32_t pktseqno = packet.getSeqNo(); @@ -828,7 +828,7 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) return -1; } - const srt::CPacket& pkt = m_entries[p].pUnit->m_Packet; + const srt::CPacket& pkt = packetAt(p); if (bTsbPdEnabled) { @@ -935,8 +935,8 @@ int CRcvBuffer::getTimespan_ms() const return 0; const steady_clock::time_point startstamp = - getPktTsbPdTime(m_entries[startpos].pUnit->m_Packet.getMsgTimeStamp()); - const steady_clock::time_point endstamp = getPktTsbPdTime(m_entries[lastpos].pUnit->m_Packet.getMsgTimeStamp()); + getPktTsbPdTime(packetAt(startpos).getMsgTimeStamp()); + const steady_clock::time_point endstamp = getPktTsbPdTime(packetAt(lastpos).getMsgTimeStamp()); if (endstamp < startstamp) return 0; @@ -1006,7 +1006,7 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstReadablePacketInfo(time_point time_no { if (hasInorderPackets) { - const CPacket& packet = m_entries[m_iStartPos].pUnit->m_Packet; + const CPacket& packet = packetAt(m_iStartPos); const PacketInfo info = {packet.getSeqNo(), false, time_point()}; return info; } @@ -1014,7 +1014,7 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstReadablePacketInfo(time_point time_no if (m_iFirstRandomMsgPos >= 0) { SRT_ASSERT(m_numRandomPackets > 0); - const CPacket& packet = m_entries[m_iFirstRandomMsgPos].pUnit->m_Packet; + const CPacket& packet = packetAt(m_iFirstRandomMsgPos); const PacketInfo info = {packet.getSeqNo(), true, time_point()}; return info; } @@ -1055,9 +1055,9 @@ bool CRcvBuffer::dropUnitInPos(int pos) return false; if (m_tsbpd.isEnabled()) { - updateTsbPdTimeBase(m_entries[pos].pUnit->m_Packet.getMsgTimeStamp()); + updateTsbPdTimeBase(packetAt(pos).getMsgTimeStamp()); } - else if (m_bMessageAPI && !m_entries[pos].pUnit->m_Packet.getMsgOrderFlag()) + else if (m_bMessageAPI && !packetAt(pos).getMsgOrderFlag()) { --m_numRandomPackets; if (pos == m_iFirstRandomMsgPos) @@ -1128,7 +1128,7 @@ int CRcvBuffer::findLastMessagePkt() { SRT_ASSERT(m_entries[i].pUnit); - if (m_entries[i].pUnit->m_Packet.getMsgBoundary() & PB_LAST) + if (packetAt(i).getMsgBoundary() & PB_LAST) { return i; } @@ -1193,7 +1193,7 @@ bool CRcvBuffer::checkFirstReadableRandom() if (!m_entries[pos].pUnit) return false; - const CPacket& pkt = m_entries[pos].pUnit->m_Packet; + const CPacket& pkt = packetAt(pos); if (pkt.getMsgOrderFlag()) return false; @@ -1236,7 +1236,7 @@ void CRcvBuffer::updateFirstReadableRandom() continue; } - const CPacket& pkt = m_entries[pos].pUnit->m_Packet; + const CPacket& pkt = packetAt(pos); if (pkt.getMsgOrderFlag()) // Skip in order packet { @@ -1287,7 +1287,7 @@ int CRcvBuffer::scanNotInOrderMessageRight(const int startPos, int msgNo) const if (!m_entries[pos].pUnit) break; - const CPacket& pkt = m_entries[pos].pUnit->m_Packet; + const CPacket& pkt = packetAt(pos); if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { @@ -1376,7 +1376,7 @@ string CRcvBuffer::strFullnessState(bool enable_debug_log, int iFirstUnackSeqNo, if (enable_debug_log) { ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo - << " m_iStartPos=" << m_iStartPos << " m_iMaxPosInc=" << m_iMaxPosOff << ". "; + << " m_iStartPos=" << m_iStartPos << " m_iMaxPosOff=" << m_iMaxPosOff << ". "; } ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << m_szSize << " pkts. "; @@ -1392,7 +1392,7 @@ string CRcvBuffer::strFullnessState(bool enable_debug_log, int iFirstUnackSeqNo, if (m_entries[iLastPos].pUnit) { ss << ", timespan "; - const uint32_t usPktTimestamp = m_entries[iLastPos].pUnit->m_Packet.getMsgTimeStamp(); + const uint32_t usPktTimestamp = packetAt(iLastPos).getMsgTimeStamp(); ss << count_milliseconds(m_tsbpd.getPktTsbPdTime(usPktTimestamp) - nextValidPkt.tsbpd_time); ss << " ms"; } From 8335cbec1a7477a387be35f470e068779ccef41a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 10 Nov 2022 14:58:42 +0100 Subject: [PATCH 33/62] Fixed some logs formatting --- srtcore/core.cpp | 34 +++++++++++++++++++++++----------- srtcore/packet.cpp | 22 ++++++++++++++++++---- srtcore/queue.cpp | 4 ++-- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index b480acbd3..950cb0b72 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -5274,11 +5274,20 @@ void * srt::CUDT::tsbpd(void* param) const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); tsNextDelivery = info.tsbpd_time; - HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: packet check: %" - << info.seqno << " T=" << FormatTime(tsNextDelivery) - << " diff-now-playtime=" << FormatDuration(tnow - tsNextDelivery) - << " ready=" << is_time_to_deliver - << " ondrop=" << info.seq_gap); +#if ENABLE_HEAVY_LOGGING + if (info.seqno == SRT_SEQNO_NONE) + { + HLOGC(tslog.Debug, log << self->CONID() << "sok/tsbpd: packet check: NO PACKETS"); + } + else + { + HLOGC(tslog.Debug, log << self->CONID() << "sok/tsbpd: packet check: %" + << info.seqno << " T=" << FormatTime(tsNextDelivery) + << " diff-now-playtime=" << FormatDuration(tnow - tsNextDelivery) + << " ready=" << is_time_to_deliver + << " ondrop=" << info.seq_gap); + } +#endif if (!self->m_bTLPktDrop) { @@ -5316,7 +5325,7 @@ void * srt::CUDT::tsbpd(void* param) { HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << info.seqno << " (belated " - << (count_milliseconds(steady_clock::now() - info.tsbpd_time)) << "ms)"); + << FormatDuration(steady_clock::now() - info.tsbpd_time) << ")"); /* * There are packets ready to be delivered * signal a waiting "recv" call if there is any data available @@ -5375,6 +5384,8 @@ void * srt::CUDT::tsbpd(void* param) tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } + SRT_ATR_UNUSED bool wakeup_on_signal = true; + if (!is_zero(tsNextDelivery)) { IF_HEAVY_LOGGING(const steady_clock::duration timediff = tsNextDelivery - tnow); @@ -5385,9 +5396,9 @@ void * srt::CUDT::tsbpd(void* param) self->m_bWakeOnRecv = false; HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << info.seqno - << " T=" << FormatTime(tsNextDelivery) << " - waiting " << count_milliseconds(timediff) << "ms"); + << " T=" << FormatTime(tsNextDelivery) << " - waiting " << FormatDuration(timediff)); THREAD_PAUSED(); - tsbpd_cc.wait_until(tsNextDelivery); + wakeup_on_signal = tsbpd_cc.wait_until(tsNextDelivery); THREAD_RESUMED(); } else @@ -5410,7 +5421,8 @@ void * srt::CUDT::tsbpd(void* param) THREAD_RESUMED(); } - HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP!!!"); + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP [" << (wakeup_on_signal ? "signal" : "timeout") << "]!!! - " + << "NOW=" << FormatTime(steady_clock::now())); } THREAD_EXIT(); HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: EXITING"); @@ -7488,8 +7500,8 @@ bool srt::CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) m_dCongestionWindow = m_CongCtl->cgWindowSize(); #if ENABLE_HEAVY_LOGGING HLOGC(rslog.Debug, - log << CONID() << "updateCC: updated values from congctl: interval=" << count_microseconds(m_tdSendInterval) << " us (" - << "tk (" << m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" + log << CONID() << "updateCC: updated values from congctl: interval=" << FormatDuration(m_tdSendInterval) + << " (cfg:" << m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" << std::setprecision(3) << m_dCongestionWindow); #endif } diff --git a/srtcore/packet.cpp b/srtcore/packet.cpp index cbe4dd90d..e7ddb9db1 100644 --- a/srtcore/packet.cpp +++ b/srtcore/packet.cpp @@ -263,7 +263,7 @@ void CPacket::setLength(size_t len, size_t cap) #if ENABLE_HEAVY_LOGGING // Debug only -static std::string FormatNumbers(UDTMessageType pkttype, const int32_t* lparam, void* rparam, size_t size) +static std::string FormatNumbers(UDTMessageType pkttype, const int32_t* lparam, void* rparam, const size_t size) { // This may be changed over time, so use special interpretation // only for certain types, and still display all data, no matter @@ -288,9 +288,15 @@ static std::string FormatNumbers(UDTMessageType pkttype, const int32_t* lparam, } bool interp_as_seq = (pkttype == UMSG_LOSSREPORT || pkttype == UMSG_DROPREQ); + bool display_dec = (pkttype == UMSG_ACK || pkttype == UMSG_ACKACK || pkttype == UMSG_DROPREQ); out << " [ "; - for (size_t i = 0; i < size; ++i) + + // Will be effective only for hex/oct. + out << std::showbase; + + const size_t size32 = size/4; + for (size_t i = 0; i < size32; ++i) { int32_t val = ((int32_t*)rparam)[i]; if (interp_as_seq) @@ -302,8 +308,16 @@ static std::string FormatNumbers(UDTMessageType pkttype, const int32_t* lparam, } else { - out << std::showpos << std::hex << val << "/" << std::dec << val; + if (!display_dec) + { + out << std::hex; + out << val << "/"; + out << std::dec; + } + out << val; + } + out << " "; } out << "]"; @@ -315,7 +329,7 @@ void CPacket::pack(UDTMessageType pkttype, const int32_t* lparam, void* rparam, { // Set (bit-0 = 1) and (bit-1~15 = type) setControl(pkttype); - HLOGC(inlog.Debug, log << "pack: type=" << MessageTypeStr(pkttype) << FormatNumbers(pkttype, lparam, rparam, size)); + HLOGC(inlog.Debug, log << "pack: type=" << MessageTypeStr(pkttype) << " " << FormatNumbers(pkttype, lparam, rparam, size)); // Set additional information and control information field switch (pkttype) diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 8d6727268..1627a0c6b 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -1042,8 +1042,8 @@ bool srt::CRendezvousQueue::qualifyToHandle(EReadStatus rst, if ((rst == RST_AGAIN || i->m_iID != iDstSockID) && tsNow <= tsRepeat) { HLOGC(cnlog.Debug, - log << "RID:@" << i->m_iID << std::fixed << count_microseconds(tsNow - tsLastReq) / 1000.0 - << " ms passed since last connection request."); + log << "RID:@" << i->m_iID << " " << FormatDuration(tsNow - tsLastReq) + << " passed since last connection request."); continue; } From 631adb0e9813561d1dfad5c6b6557f5a50fea131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Wed, 7 Dec 2022 13:44:28 +0100 Subject: [PATCH 34/62] Some cosmetics and updates from upstream --- srtcore/api.cpp | 12 +++--- srtcore/buffer_rcv.cpp | 38 ++++++++--------- srtcore/core.cpp | 70 ++++++++++++++++++++----------- srtcore/group.cpp | 86 +++++++++++++++++++------------------- srtcore/group.h | 2 +- srtcore/list.h | 1 - srtcore/queue.cpp | 36 ++++++++++++++-- test/test_losslist_rcv.cpp | 66 +++++++++++++++++++++++++++++ 8 files changed, 211 insertions(+), 100 deletions(-) diff --git a/srtcore/api.cpp b/srtcore/api.cpp index a65c62c37..1b8f03e6f 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -150,13 +150,11 @@ bool srt::CUDTSocket::readReady() { #if ENABLE_BONDING - // If this is a group member socket, then reading - // happens exclusively from the group and the socket is - // only used as a connection point, packet dispatching - // and single link management. Data buffering and hence - // ability to deliver a packet through API is exclusively - // the matter of group, therefore a single socket is never - // "read ready". + // If this is a group member socket, then reading happens exclusively from + // the group and the socket is only used as a connection point, packet + // dispatching and single link management. Data buffering and hence ability + // to deliver a packet through API is exclusively the matter of group, + // therefore a single socket is never "read ready". if (m_GroupOf) return false; diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index eb177086e..e0e809577 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -75,15 +75,15 @@ namespace { #define IF_RCVBUF_DEBUG(instr) (void)0 - // Check if iFirstNonreadPos is in range [iStartPos, (iStartPos + iMaxPosInc) % iSize]. + // 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. - bool isInRange(int iStartPos, int iMaxPosInc, size_t iSize, int iFirstNonreadPos) + bool isInRange(int iStartPos, int iMaxPosOff, size_t iSize, int iFirstNonreadPos) { if (iFirstNonreadPos == iStartPos) return true; - const int iLastPos = (iStartPos + iMaxPosInc) % iSize; + const int iLastPos = (iStartPos + iMaxPosOff) % iSize; const bool isOverrun = iLastPos < iStartPos; if (isOverrun) @@ -499,7 +499,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) continue; // TODO: Break the loop if a massege has been found. No need to search further. - const int32_t msgseq = m_entries[i].pUnit->m_Packet.getMsgSeq(m_bPeerRexmitFlag); + const int32_t msgseq = packetAt(i).getMsgSeq(m_bPeerRexmitFlag); if (msgseq == msgno) { ++iDropCnt; @@ -551,7 +551,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) { // Don't drop messages, if all its packets are already in the buffer. // TODO: Don't drop a several-packet message if all packets are in the buffer. - if (m_entries[i].pUnit && m_entries[i].pUnit->m_Packet.getMsgBoundary() == PB_SOLO) + if (m_entries[i].pUnit && packetAt(i).getMsgBoundary() == PB_SOLO) continue; dropUnitInPos(i); @@ -636,7 +636,7 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pairm_Packet; + const CPacket& packet = packetAt(i); const size_t pktsize = packet.getLength(); const int32_t pktseqno = packet.getSeqNo(); @@ -828,7 +828,7 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) return -1; } - const srt::CPacket& pkt = m_entries[p].pUnit->m_Packet; + const srt::CPacket& pkt = packetAt(p); if (bTsbPdEnabled) { @@ -935,8 +935,8 @@ int CRcvBuffer::getTimespan_ms() const return 0; const steady_clock::time_point startstamp = - getPktTsbPdTime(m_entries[startpos].pUnit->m_Packet.getMsgTimeStamp()); - const steady_clock::time_point endstamp = getPktTsbPdTime(m_entries[lastpos].pUnit->m_Packet.getMsgTimeStamp()); + getPktTsbPdTime(packetAt(startpos).getMsgTimeStamp()); + const steady_clock::time_point endstamp = getPktTsbPdTime(packetAt(lastpos).getMsgTimeStamp()); if (endstamp < startstamp) return 0; @@ -1006,7 +1006,7 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstReadablePacketInfo(time_point time_no { if (hasInorderPackets) { - const CPacket& packet = m_entries[m_iStartPos].pUnit->m_Packet; + const CPacket& packet = packetAt(m_iStartPos); const PacketInfo info = {packet.getSeqNo(), false, time_point()}; return info; } @@ -1014,7 +1014,7 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstReadablePacketInfo(time_point time_no if (m_iFirstRandomMsgPos >= 0) { SRT_ASSERT(m_numRandomPackets > 0); - const CPacket& packet = m_entries[m_iFirstRandomMsgPos].pUnit->m_Packet; + const CPacket& packet = packetAt(m_iFirstRandomMsgPos); const PacketInfo info = {packet.getSeqNo(), true, time_point()}; return info; } @@ -1055,9 +1055,9 @@ bool CRcvBuffer::dropUnitInPos(int pos) return false; if (m_tsbpd.isEnabled()) { - updateTsbPdTimeBase(m_entries[pos].pUnit->m_Packet.getMsgTimeStamp()); + updateTsbPdTimeBase(packetAt(pos).getMsgTimeStamp()); } - else if (m_bMessageAPI && !m_entries[pos].pUnit->m_Packet.getMsgOrderFlag()) + else if (m_bMessageAPI && !packetAt(pos).getMsgOrderFlag()) { --m_numRandomPackets; if (pos == m_iFirstRandomMsgPos) @@ -1128,7 +1128,7 @@ int CRcvBuffer::findLastMessagePkt() { SRT_ASSERT(m_entries[i].pUnit); - if (m_entries[i].pUnit->m_Packet.getMsgBoundary() & PB_LAST) + if (packetAt(i).getMsgBoundary() & PB_LAST) { return i; } @@ -1193,7 +1193,7 @@ bool CRcvBuffer::checkFirstReadableRandom() if (!m_entries[pos].pUnit) return false; - const CPacket& pkt = m_entries[pos].pUnit->m_Packet; + const CPacket& pkt = packetAt(pos); if (pkt.getMsgOrderFlag()) return false; @@ -1236,7 +1236,7 @@ void CRcvBuffer::updateFirstReadableRandom() continue; } - const CPacket& pkt = m_entries[pos].pUnit->m_Packet; + const CPacket& pkt = packetAt(pos); if (pkt.getMsgOrderFlag()) // Skip in order packet { @@ -1287,7 +1287,7 @@ int CRcvBuffer::scanNotInOrderMessageRight(const int startPos, int msgNo) const if (!m_entries[pos].pUnit) break; - const CPacket& pkt = m_entries[pos].pUnit->m_Packet; + const CPacket& pkt = packetAt(pos); if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { @@ -1374,7 +1374,7 @@ string CRcvBuffer::strFullnessState(int iFirstUnackSeqNo, const time_point& tsNo stringstream ss; ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo - << " m_iStartPos=" << m_iStartPos << " m_iMaxPosInc=" << m_iMaxPosOff << ". "; + << " m_iStartPos=" << m_iStartPos << " m_iMaxPosOff=" << m_iMaxPosOff << ". "; ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << m_szSize << " pkts. "; @@ -1389,7 +1389,7 @@ string CRcvBuffer::strFullnessState(int iFirstUnackSeqNo, const time_point& tsNo if (m_entries[iLastPos].pUnit) { ss << ", timespan "; - const uint32_t usPktTimestamp = m_entries[iLastPos].pUnit->m_Packet.getMsgTimeStamp(); + const uint32_t usPktTimestamp = packetAt(iLastPos).getMsgTimeStamp(); ss << count_milliseconds(m_tsbpd.getPktTsbPdTime(usPktTimestamp) - nextValidPkt.tsbpd_time); ss << " ms"; } diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 630ac6a92..cd394af81 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -5261,11 +5261,20 @@ void * srt::CUDT::tsbpd(void* param) const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); tsNextDelivery = info.tsbpd_time; - HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: packet check: %" - << info.seqno << " T=" << FormatTime(tsNextDelivery) - << " diff-now-playtime=" << FormatDuration(tnow - tsNextDelivery) - << " ready=" << is_time_to_deliver - << " ondrop=" << info.seq_gap); +#if ENABLE_HEAVY_LOGGING + if (info.seqno == SRT_SEQNO_NONE) + { + HLOGC(tslog.Debug, log << self->CONID() << "sok/tsbpd: packet check: NO PACKETS"); + } + else + { + HLOGC(tslog.Debug, log << self->CONID() << "sok/tsbpd: packet check: %" + << info.seqno << " T=" << FormatTime(tsNextDelivery) + << " diff-now-playtime=" << FormatDuration(tnow - tsNextDelivery) + << " ready=" << is_time_to_deliver + << " ondrop=" << info.seq_gap); + } +#endif if (!self->m_bTLPktDrop) { @@ -5300,7 +5309,7 @@ void * srt::CUDT::tsbpd(void* param) { HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << info.seqno << " (belated " - << (count_milliseconds(steady_clock::now() - info.tsbpd_time)) << "ms)"); + << FormatDuration(steady_clock::now() - info.tsbpd_time) << ")"); /* * There are packets ready to be delivered * signal a waiting "recv" call if there is any data available @@ -5359,6 +5368,8 @@ void * srt::CUDT::tsbpd(void* param) tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } + SRT_ATR_UNUSED bool wakeup_on_signal = true; + if (!is_zero(tsNextDelivery)) { IF_HEAVY_LOGGING(const steady_clock::duration timediff = tsNextDelivery - tnow); @@ -5369,9 +5380,9 @@ void * srt::CUDT::tsbpd(void* param) self->m_bWakeOnRecv = false; HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << info.seqno - << " T=" << FormatTime(tsNextDelivery) << " - waiting " << count_milliseconds(timediff) << "ms"); + << " T=" << FormatTime(tsNextDelivery) << " - waiting " << FormatDuration(timediff)); THREAD_PAUSED(); - tsbpd_cc.wait_until(tsNextDelivery); + wakeup_on_signal = tsbpd_cc.wait_until(tsNextDelivery); THREAD_RESUMED(); } else @@ -5394,7 +5405,8 @@ void * srt::CUDT::tsbpd(void* param) THREAD_RESUMED(); } - HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP!!!"); + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP [" << (wakeup_on_signal ? "signal" : "timeout") << "]!!! - " + << "NOW=" << FormatTime(steady_clock::now())); } THREAD_EXIT(); HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: EXITING"); @@ -6680,8 +6692,7 @@ size_t srt::CUDT::getAvailRcvBufferSizeNoLock() const bool srt::CUDT::isRcvBufferReady() const { #if ENABLE_BONDING - // In "new bonding" (new rcvbuf + bonding) - // the group member socket is never read-ready. This function is called + // The group member socket is never read-ready. This function is called // from various parts, not always exactly necessary, but it's // too complicated to untangle it without refaxing the epoll system. @@ -6717,9 +6728,7 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0); #if ENABLE_BONDING - // In this code configuration, this function shall not be used - // if the socket is a group member. - + // This function shall not be used if the socket is a group member. if (m_parent->m_GroupOf) throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI); #endif @@ -7489,8 +7498,8 @@ bool srt::CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) m_dCongestionWindow = m_CongCtl->cgWindowSize(); #if ENABLE_HEAVY_LOGGING HLOGC(rslog.Debug, - log << CONID() << "updateCC: updated values from congctl: interval=" << count_microseconds(m_tdSendInterval) << " us (" - << "tk (" << m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" + log << CONID() << "updateCC: updated values from congctl: interval=" << FormatDuration(m_tdSendInterval) + << " (cfg:" << m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" << std::setprecision(3) << m_dCongestionWindow); #endif } @@ -7755,7 +7764,7 @@ bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) } #endif - SRT_ASSERT(!!m_pRcvBuffer); + SRT_ASSERT(!! m_pRcvBuffer); if (!m_pRcvBuffer) { LOGP(cnlog.Error, "IPE: ack can't be sent, buffer doesn't exist and no group membership"); @@ -7769,7 +7778,7 @@ bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) else w_log_reason = "expected next"; } - + HLOGC(xtlog.Debug, log << "NONCONT-SEQUENCE: " << w_log_reason << " %" << w_seq); return true; } @@ -7952,6 +7961,8 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) SRT_ASSERT( bool(m_parent->m_GroupOf) != bool(m_pRcvBuffer) ); if (m_pRcvBuffer && isRcvBufferReady()) +#else + if (isRcvBufferReady()) #endif { if (m_config.bSynRecving) @@ -7977,6 +7988,15 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) // This is done only for "old bonding" using the app-reader procedure. // In the new bonding all buffer reception and reading ready state update // happen exclusively inside the group. + // XXX Note that this is only true in TSBPD mode. In file mode, the ACK + // signoff is still in force, maybe not for the buffer, but still for + // the read state update, where the read declaration is done only when + // ACK has moved the ACK pointer some sequences in forward. The problem with + // properly implementing this is that reading happens from the group and + // it is done directly from the group buffer (without any involvement of + // the socket), but ACK action is a timer-loop action executed by a socket. + // These activities happen on two different timers and on two different + // moments, therefore likely it must be implemented somehow in the group. /* if (group_read_seq != SRT_SEQNO_NONE && m_parent->m_GroupOf) @@ -8328,11 +8348,11 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) { // Update Flow Window Size, must update before and together with m_iSndLastAck - m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; - m_iSndLastAck = ackdata_seqno; - m_iSndMinFlightSpan = getFlightSpan(); + m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; + m_iSndLastAck = ackdata_seqno; + m_iSndMinFlightSpan = getFlightSpan(); m_tsLastRspAckTime = currtime; - m_iReXmitCount = 1; // Reset re-transmit count since last ACK + m_iReXmitCount = 1; // Reset re-transmit count since last ACK } /* @@ -10183,7 +10203,7 @@ int srt::CUDT::checkLazySpawnTsbPdThread() // Shipped to the group function because this will // likely require groupwise locking. - return m_parent->m_GroupOf->checkLazySpawnLatencyThread(); + return m_parent->m_GroupOf->checkLazySpawnTsbPdThread(); } #endif @@ -10887,7 +10907,7 @@ int srt::CUDT::processData(CUnit* in_unit) // Needed for possibly check for needsQuickACK. const bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.m_iSeqNo, m_pRcvBuffer->getStartSeqNo()) < 0); - int res = handleSocketPacketReception(incoming, + const int res = handleSocketPacketReception(incoming, (new_inserted), (next_tsbpd_avail), (was_sent_in_order), @@ -10951,7 +10971,7 @@ int srt::CUDT::processData(CUnit* in_unit) { return -1; } - } + } // End of recvbuf_acklock if (m_bClosing) { diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 83b0cc7d6..7fe31e14f 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -1073,6 +1073,7 @@ void CUDTGroup::synchronizeLoss(int32_t seqno) for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { CUDT& u = gi->ps->core(); + // XXX Consider expanding this call in place. u.skipMemberLoss(seqno); } } @@ -2888,6 +2889,47 @@ const char* CUDTGroup::StateStr(CUDTGroup::GroupState st) } +void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) +{ + if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + if (m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + const steady_clock::time_point currtime = steady_clock::now(); + + memset(perf, 0, sizeof *perf); + + ScopedLock gg(m_GroupLock); + + perf->msTimeStamp = count_milliseconds(currtime - m_tsStartTime); + + perf->pktSentUnique = m_stats.sent.trace.count(); + perf->pktRecvUnique = m_stats.recv.trace.count(); + perf->pktRcvDrop = m_stats.recvDrop.trace.count(); + + perf->byteSentUnique = m_stats.sent.trace.bytesWithHdr(); + perf->byteRecvUnique = m_stats.recv.trace.bytesWithHdr(); + perf->byteRcvDrop = m_stats.recvDrop.trace.bytesWithHdr(); + + perf->pktSentUniqueTotal = m_stats.sent.total.count(); + perf->pktRecvUniqueTotal = m_stats.recv.total.count(); + perf->pktRcvDropTotal = m_stats.recvDrop.total.count(); + + perf->byteSentUniqueTotal = m_stats.sent.total.bytesWithHdr(); + perf->byteRecvUniqueTotal = m_stats.recv.total.bytesWithHdr(); + perf->byteRcvDropTotal = m_stats.recvDrop.total.bytesWithHdr(); + + const double interval = static_cast(count_microseconds(currtime - m_stats.tsLastSampleTime)); + perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval; + perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval; + + if (clear) + { + m_stats.reset(); + } +} + // The REAL version for the new group receiver. // int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) @@ -3140,48 +3182,6 @@ int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) return res; } - -void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) -{ - if (!m_bConnected) - throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); - if (m_bClosing) - throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); - - const steady_clock::time_point currtime = steady_clock::now(); - - memset(perf, 0, sizeof *perf); - - ScopedLock gg(m_GroupLock); - - perf->msTimeStamp = count_milliseconds(currtime - m_tsStartTime); - - perf->pktSentUnique = m_stats.sent.trace.count(); - perf->pktRecvUnique = m_stats.recv.trace.count(); - perf->pktRcvDrop = m_stats.recvDrop.trace.count(); - - perf->byteSentUnique = m_stats.sent.trace.bytesWithHdr(); - perf->byteRecvUnique = m_stats.recv.trace.bytesWithHdr(); - perf->byteRcvDrop = m_stats.recvDrop.trace.bytesWithHdr(); - - perf->pktSentUniqueTotal = m_stats.sent.total.count(); - perf->pktRecvUniqueTotal = m_stats.recv.total.count(); - perf->pktRcvDropTotal = m_stats.recvDrop.total.count(); - - perf->byteSentUniqueTotal = m_stats.sent.total.bytesWithHdr(); - perf->byteRecvUniqueTotal = m_stats.recv.total.bytesWithHdr(); - perf->byteRcvDropTotal = m_stats.recvDrop.total.bytesWithHdr(); - - const double interval = static_cast(count_microseconds(currtime - m_stats.tsLastSampleTime)); - perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval; - perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval; - - if (clear) - { - m_stats.reset(); - } -} - /// @brief Compares group members by their weight (higher weight comes first). struct FCompareByWeight { @@ -5897,7 +5897,7 @@ void CUDTGroup::discardSendSchedule(SocketData* d, int ndiscard) // Receiver part -int CUDTGroup::checkLazySpawnLatencyThread() +int CUDTGroup::checkLazySpawnTsbPdThread() { // It is confirmed that the TSBPD thread is required, // so just check if it's running already. diff --git a/srtcore/group.h b/srtcore/group.h index 08cd2aa37..7b7e100f5 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -912,7 +912,7 @@ class CUDTGroup #endif } - int checkLazySpawnLatencyThread(); + int checkLazySpawnTsbPdThread(); CRcvBuffer::InsertInfo addDataUnit(SocketData* member, CUnit* u, CUDT::loss_seqs_t&, bool&); bool checkPacketArrivalLoss(SocketData* member, const CPacket& rpkt, CUDT::loss_seqs_t&); bool checkBalancingLoss(const CPacket& rpkt, CUDT::loss_seqs_t&); diff --git a/srtcore/list.h b/srtcore/list.h index 54327f0ca..2503d0db1 100644 --- a/srtcore/list.h +++ b/srtcore/list.h @@ -310,7 +310,6 @@ struct CRcvFreshLoss static bool removeOne(std::deque& w_container, int32_t sequence, int* had_ttl = NULL); }; - } // namespace srt #endif diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 6e690b79c..80820d945 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -833,14 +833,42 @@ srt::CUDT* srt::CRendezvousQueue::retrieve(const sockaddr_any& addr, SRTSOCKET& { ScopedLock vg(m_RIDListLock); + IF_HEAVY_LOGGING(const char* const id_type = w_id ? "THIS ID" : "A NEW CONNECTION"); + // TODO: optimize search for (list::const_iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) { if (i->m_PeerAddr == addr && ((w_id == 0) || (w_id == i->m_iID))) { + // This procedure doesn't exactly respond to the original UDT idea. + // As the "rendezvous queue" is used for both handling rendezous and + // the caller sockets in the non-blocking mode (for blocking mode the + // entire handshake procedure is handled in a loop-style in CUDT::startConnect), + // the RID list should give up a socket entity in the following cases: + // 1. For THE SAME id as passed in w_id, respond always, as per a caller + // socket that is currently trying to connect and is managed with + // HS roundtrips in an event-style. Same for rendezvous. + // 2. For the "connection request" ID=0 the found socket should be given up + // ONLY IF it is rendezvous. Normally ID=0 is only for listener as a + // connection request. But if there was a listener, then this function + // wouldn't even be called, as this case would be handled before trying + // to call this function. + // + // This means: if an incoming ID is 0, then this search should succeed ONLY + // IF THE FOUND SOCKET WAS RENDEZVOUS. + + if (!w_id && !i->m_pUDT->m_config.bRendezvous) + { + HLOGC(cnlog.Debug, + log << "RID: found id @" << i->m_iID << " while looking for " + << id_type << " FROM " << i->m_PeerAddr.str() + << ", but it's NOT RENDEZVOUS, skipping"); + continue; + } + HLOGC(cnlog.Debug, - log << "RID: found id @" << i->m_iID << " while looking for " - << (w_id ? "THIS ID FROM " : "A NEW CONNECTION FROM ") << i->m_PeerAddr.str()); + log << "RID: found id @" << i->m_iID << " while looking for " + << id_type << " FROM " << i->m_PeerAddr.str()); w_id = i->m_iID; return i->m_pUDT; } @@ -1060,8 +1088,8 @@ bool srt::CRendezvousQueue::qualifyToHandle(EReadStatus rst, if ((rst == RST_AGAIN || i->m_iID != iDstSockID) && tsNow <= tsRepeat) { HLOGC(cnlog.Debug, - log << "RID:@" << i->m_iID << std::fixed << count_microseconds(tsNow - tsLastReq) / 1000.0 - << " ms passed since last connection request."); + log << "RID:@" << i->m_iID << " " << FormatDuration(tsNow - tsLastReq) + << " passed since last connection request."); continue; } diff --git a/test/test_losslist_rcv.cpp b/test/test_losslist_rcv.cpp index d42870ec0..03a308829 100644 --- a/test/test_losslist_rcv.cpp +++ b/test/test_losslist_rcv.cpp @@ -69,3 +69,69 @@ TEST_F(CRcvLossListTest, InsertTwoElemsEdge) EXPECT_TRUE(m_lossList->remove(CSeqNo::m_iMaxSeqNo, 1)); CheckEmptyArray(); } + +TEST(CRcvFreshLossListTest, CheckFreshLossList) +{ + std::deque floss { + CRcvFreshLoss (10, 15, 5), + CRcvFreshLoss (25, 29, 10), + CRcvFreshLoss (30, 30, 3), + CRcvFreshLoss (45, 80, 100) + }; + + EXPECT_EQ(floss.size(), 4); + + // Ok, now let's do element removal + + int had_ttl = 0; + bool rm = CRcvFreshLoss::removeOne((floss), 26, &had_ttl); + + EXPECT_EQ(rm, true); + EXPECT_EQ(had_ttl, 10); + EXPECT_EQ(floss.size(), 5); + + // Now we expect to have [10-15] [25-25] [27-35]... + // After revoking 25 it should have removed it. + + // SPLIT + rm = CRcvFreshLoss::removeOne((floss), 27, &had_ttl); + EXPECT_EQ(rm, true); + EXPECT_EQ(had_ttl, 10); + EXPECT_EQ(floss.size(), 5); + + // STRIP + rm = CRcvFreshLoss::removeOne((floss), 28, &had_ttl); + EXPECT_EQ(rm, true); + EXPECT_EQ(had_ttl, 10); + EXPECT_EQ(floss.size(), 5); + + // DELETE + rm = CRcvFreshLoss::removeOne((floss), 25, &had_ttl); + EXPECT_EQ(rm, true); + EXPECT_EQ(had_ttl, 10); + EXPECT_EQ(floss.size(), 4); + + // SPLIT + rm = CRcvFreshLoss::removeOne((floss), 50, &had_ttl); + EXPECT_EQ(rm, true); + EXPECT_EQ(had_ttl, 100); + EXPECT_EQ(floss.size(), 5); + + // DELETE + rm = CRcvFreshLoss::removeOne((floss), 30, &had_ttl); + EXPECT_EQ(rm, true); + EXPECT_EQ(had_ttl, 3); + EXPECT_EQ(floss.size(), 4); + + // Remove nonexistent sequence, but existing before. + rm = CRcvFreshLoss::removeOne((floss), 25, NULL); + EXPECT_EQ(rm, false); + EXPECT_EQ(floss.size(), 4); + + // Remove nonexistent sequence that didn't exist before. + rm = CRcvFreshLoss::removeOne((floss), 31, &had_ttl); + EXPECT_EQ(rm, false); + EXPECT_EQ(had_ttl, 0); + EXPECT_EQ(floss.size(), 4); + +} From db184350cc0dc96b54204e477fd3c1df87bfc6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Wed, 7 Dec 2022 18:36:10 +0100 Subject: [PATCH 35/62] Fixed atomic operator bug (reported by gcc-11) --- srtcore/group.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 7fe31e14f..2b7064dae 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -1149,7 +1149,7 @@ bool CUDTGroup::checkPacketArrivalLoss(SocketData* member, const CPacket& rpkt, dist = max(m_zLongestDistance, dist); m_zLongestDistance = dist; - duration td = steady_clock::now() - m_RcvFurthestPacketTime; + duration td = steady_clock::now() - m_RcvFurthestPacketTime.load(); td = max(m_tdLongestDistance.load(), td); m_tdLongestDistance = td; From 9f7db49944e1a908625a977579b02ab9f59edadc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 8 Dec 2022 15:16:24 +0100 Subject: [PATCH 36/62] Fixed: for backup link sync use the common group buffer start sequence --- srtcore/core.cpp | 26 ++------------------------ srtcore/core.h | 2 +- srtcore/group.cpp | 9 ++++++++- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 68a3b5bc2..f914edba9 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -11191,30 +11191,8 @@ int srt::CUDT::processData(CUnit* in_unit) // if the transmission was already torn in the previously active link // this shouldn't be a problem that these packets won't be recovered // after activating the second link, although will be retried this way. -void srt::CUDT::updateIdleLinkFrom(CUDT* source) +void srt::CUDT::updateIdleLinkFrom(int32_t new_last_rcv, SRTSOCKET id SRT_ATR_UNUSED /* logging only */) { - int bufseq; - { - ScopedLock lg (m_RcvBufferLock); - bufseq = source->m_pRcvBuffer->getStartSeqNo(); - } - ScopedLock lg (m_RecvLock); - - // Same version as the one with the old receiver buffering, - // just this time don't check if the buffer is empty. The value - // will be rejected anyway if it's equal. - - int32_t new_last_rcv = source->m_iRcvLastAck; - - if (CSeqNo::seqcmp(new_last_rcv, bufseq) < 0) - { - // Emergency check whether the last ACK was behind the - // buffer. This may happen when TSBPD dropped empty cells. - // This may cause that the newly activated link may derive - // these empty cells which will never be recovered. - new_last_rcv = bufseq; - } - // if (new_last_rcv <=% m_iRcvCurrSeqNo) if (CSeqNo::seqcmp(new_last_rcv, m_iRcvCurrSeqNo) <= 0) { @@ -11226,7 +11204,7 @@ void srt::CUDT::updateIdleLinkFrom(CUDT* source) } HLOGC(grlog.Debug, log << "grp: updating rcv-seq in @" << m_SocketID - << " from @" << source->m_SocketID << ": %" << new_last_rcv); + << " from @" << id << ": %" << new_last_rcv); setInitialRcvSeq(new_last_rcv); } diff --git a/srtcore/core.h b/srtcore/core.h index 6c7c50960..56aec7b09 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -544,7 +544,7 @@ class CUDT void updateSrtRcvSettings(); void updateSrtSndSettings(); - void updateIdleLinkFrom(CUDT* source); + void updateIdleLinkFrom(int32_t seq, SRTSOCKET id); /// @brief Drop packets too late to be delivered if any. /// @returns the number of packets actually dropped. diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 2b7064dae..46c9da00a 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -5163,9 +5163,16 @@ void CUDTGroup::updateLatestRcv(CUDTSocket* s) // operation will need receiver lock, so it might // risk a deadlock. + int bufseq; + { + ScopedLock bg (m_RcvBufferLock); + bufseq = m_pRcvBuffer->getStartSeqNo(); + } + int32_t latest_seq = CSeqNo::maxseq(bufseq, source->m_iRcvLastAck); + for (size_t i = 0; i < targets.size(); ++i) { - targets[i]->updateIdleLinkFrom(source); + targets[i]->updateIdleLinkFrom(latest_seq, source->id()); } } From 35450caccb60b47fdc03338fbf8d517c6226384a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 15 Dec 2022 11:34:59 +0100 Subject: [PATCH 37/62] Fixed deadlock on 2L locked GroupLock. Fixed crash when discarding a schedule of a broken socket. Fixed excessive sndbuf removal on rogue or excessive ACK --- srtcore/buffer_snd.cpp | 20 ++++ srtcore/buffer_snd.h | 32 +++++-- srtcore/core.cpp | 207 +++++++++++++++++++++++++++++++++-------- srtcore/core.h | 2 +- srtcore/group.cpp | 15 +-- srtcore/group.h | 6 +- 6 files changed, 225 insertions(+), 57 deletions(-) diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 4eef47932..942e0dc8d 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -539,12 +539,20 @@ void CSndBuffer::ackData(int offset) { ScopedLock bufferguard(m_BufLock); + // It is illegal to call this function without having first checked + // that the offset is within the range between 0 and the current count. + SRT_ASSERT(offset <= m_iCount); + bool move = false; for (int i = 0; i < offset; ++i) { m_iBytesCount -= m_pFirstBlock->m_iLength; if (m_pFirstBlock == m_pCurrBlock) move = true; + + // Sanity check to see if signing off for removal didn't + // exceed the last position of the used space. + SRT_ASSERT(m_pFirstBlock != m_pLastBlock); m_pFirstBlock = m_pFirstBlock->m_pNext; } if (move) @@ -555,6 +563,18 @@ void CSndBuffer::ackData(int offset) updAvgBufSize(steady_clock::now()); } +void CSndBuffer::clear() +{ + ScopedLock bufferguard(m_BufLock); + + // Keep the m_pLastBlock intact and adjust the other + // fields to it. Blocks are still linked in circle. + + m_pCurrBlock = m_pFirstBlock = m_pLastBlock; + m_iCount = 0; + m_iBytesCount = 0; +} + int CSndBuffer::getCurrBufSize() const { return m_iCount; diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index 5dce96ae0..93bcdbea3 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -152,6 +152,8 @@ class CSndBuffer void ackData(int offset); + void clear(); + /// Read size of data still in the sending list. /// @return Current size of the data in the sending list. int getCurrBufSize() const; @@ -207,12 +209,30 @@ class CSndBuffer return m_iMsgNoBitset & MSGNO_SEQ::mask; } - } * m_pBlock, *m_pFirstBlock, *m_pCurrBlock, *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) + } + /// The head pointer. Only used in constructor to roll over the initial set of blocks. + * m_pBlock, + /// The edge of the buffer at the reading end, pointing to the oldest ever stored + /// block that can still be read (although it's not readable if it is equal to m_pLastBlock). + * m_pFirstBlock, + /// The pointer to the next block to be read when extracting packets to send over the + /// network as "unique" (first time sent). Not used when extracting a packet for + /// retransmission, although this field must be updated every time any block removal + /// happens in case it would become stale. + * m_pCurrBlock, + /// Points to the block considered past-the-end of the used block space, simultaneously + /// the edge of the buffer at the writing end. The distance between m_pFirstBlock and + /// m_pLastBlock in the order of linked objects through the m_pNext field should be + /// always equal to m_iCount. + * m_pLastBlock; + + // NOTE: The actual reserved space size for the buffer is saved in m_iSize and this is + // the true number of allocated blocks. There is no pointer to point directly to the end + // of reserved space - this can only be determined by the series of buffers that are + // linked to one another through the m_pNext field, which is NULL in the last one. Note + // also that Blocks are linked in circle, while Buffers are linked up to the last one, + // and Buffers are the true holders of the pieces of memory, while a block operates on + // its fragment only, which's size is defined by m_iBlockLen. struct Buffer { diff --git a/srtcore/core.cpp b/srtcore/core.cpp index f914edba9..1ecf8859f 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -8120,18 +8120,51 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) return nbsent; } -void srt::CUDT::updateStateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seqno) +bool srt::CUDT::updateStateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seqno) { w_last_sent_seqno = m_iSndCurrSeqNo; + // For safety reasons, we can't take the ACK sequence as a good deal. + // This value must be verified and checked if: + // + // 1. The value isn't newer than the last sent, although: + // - For backup groups, we can accept ACKs that exceed the sent packets, + // however ACKs are NOT EXPECTED on an idle link. This means if such + // ACK comes we treat it as an IPE, but as fallback we shift the ACK + // position not more than to the last sent packet in the group data + // - For balancing and broadcast groups, an ACK is allowed to exceed + // the current sent sequence for a link, but still may not exceed the + // latest packet sent for the group. Group-excessive ACKs should break + // the connection, but ACKs that only exceed the link latest packet + // should reset the latest sent sequence and clear the sender buffer + // 2. The value isn't older than the newest ACK. Such packets may happen, + // but due to some random network condition and therefore can't be from + // upside treated as a rogue protocol case, only silently skipped. + // 3. The value doesn't shift ACK by more than the current size of the + // sender buffer, unless the buffer is empty. + // 4. (Proposed) The value doesn't shift ACK by more than 4 times the total + // size of the sender buffer, if the buffer is empty, in which case the link + // should be immediately broken. + // + // IMPORTANT: if the sender buffer is empty, then the base sequence number + // for it can be set to whatever value. In the old UDT implementation the + // sender buffer also didn't manage sequence numbers at all, they were set + // to packets only when they were sent. In SRT with the introduction of groups + // the management of sequence numbers was necessary for the sender buffer because + // when a packet is going to be sent over multiple links at a time (broadcast + // example), then the packet with the same payload, to be identified as the + // same packet against the receiver application, must go also with the same + // sequence number - hence sequence numbers must be dictated at the scheduling + // time and also be ready to override the existing sequence number values if + // they collide with those. + + bool valid_sndbuf_revoke = true; + #if ENABLE_BONDING CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); // This is for the call of CSndBuffer::getMsgNoAt that returns // this value as a notfound-trap. int32_t msgno_at_last_acked_seq = SRT_MSGNO_CONTROL; -#endif - -#if ENABLE_BONDING // This method is necessary for balancing groups, but it can be as well // used for BROADCAST groups, if it is decided that a loss reported on @@ -8153,12 +8186,11 @@ void srt::CUDT::updateStateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seq { // For groups of that type we use the common loss handling. - bool valid = gkeeper.group->updateOnACK(ackdata_seqno, (w_last_sent_seqno)); - if (!valid) - { - HLOGC(inlog.Debug, log << "updateStateOnACK: " << CONID() << "sent outdated ack %" << ackdata_seqno - << " against last sent %" << w_last_sent_seqno << " ??? "); - } + w_last_sent_seqno = gkeeper.group->getSentSeq(); + if (CSeqNo::seqcmp(ackdata_seqno, w_last_sent_seqno) > 0) + valid_sndbuf_revoke = false; + + gkeeper.group->updateOnACK(ackdata_seqno); // m_RecvAckLock protects sender's loss list and epoll ScopedLock ack_lock(m_RecvAckLock); @@ -8166,13 +8198,46 @@ void srt::CUDT::updateStateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seq const int offset = CSeqNo::seqoff(m_iSndLastDataAck, ackdata_seqno); // IF distance between m_iSndLastDataAck and ack is nonempty... if (offset <= 0) - return; + { + HLOGP(inlog.Debug, "ACK from the past, not checking sender buffer"); + return true; // this is from the past, but still acceptable as received ACK + } + + if (m_pSndBuffer->getCurrBufSize() > 0 && offset > m_pSndBuffer->getCurrBufSize()) + { + // Attept for buffer excess removal. + HLOGC(inlog.Debug, log << "ACK: IPE/EPE: rogue ack %" << ackdata_seqno << " offset=" << offset + << " for sender buffer size=" << m_pSndBuffer->getCurrBufSize()); + valid_sndbuf_revoke = false; + } // update sending variables m_iSndLastDataAck = ackdata_seqno; - // acknowledge the sending buffer (remove data that predate 'ack') - m_pSndBuffer->ackData(offset); + const int bufsize = m_pSndBuffer->getCurrBufSize(); + if (bufsize == 0) + { + HLOGC(inlog.Debug, log << "ACK: sndbuf buffer empty; not removing anything"); + } + else if (!valid_sndbuf_revoke) + { + HLOGC(inlog.Debug, log << "ACK: sndbuf excessive removal attempt; clearing buffer"); + m_pSndBuffer->clear(); + } + else + { + // 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); + // 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. + + // acknowledge the sending buffer (remove data that predate 'ack') + HLOGC(inlog.Debug, log << "ACK: sndbuf: removing " << offset << "/" << bufsize << " packets"); + m_pSndBuffer->ackData(offset); + } // acknowledde any waiting epolls to write uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); @@ -8180,38 +8245,65 @@ void srt::CUDT::updateStateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seq } else #endif - // Update sender's loss list and acknowledge packets in the sender's buffer { + // NOTE: This branch will be called for single socket connections + // AND in case of ENABLE_BONDING also for BACKUP groups. + // 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) - return; + { + HLOGP(inlog.Debug, "ACK from the past, not checking sender buffer"); + return true; // this is from the past, but still acceptable as received ACK + } + + if (m_pSndBuffer->getCurrBufSize() > 0 && offset > m_pSndBuffer->getCurrBufSize()) + { + // Attept for buffer excess removal. + HLOGC(inlog.Debug, log << "ACK: IPE/EPE: rogue ack %" << ackdata_seqno << " offset=" << offset + << " for sender buffer size=" << m_pSndBuffer->getCurrBufSize()); + valid_sndbuf_revoke = false; + } // update sending variables m_iSndLastDataAck = ackdata_seqno; -#if ENABLE_BONDING - if (gkeeper.group) + // remove any loss that predates 'ack' (not to be considered loss anymore) + m_pSndLossList->removeUpTo(CSeqNo::decseq(m_iSndLastDataAck)); + + const int bufsize = m_pSndBuffer->getCurrBufSize(); + if (bufsize == 0) { - // 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); - // 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. + HLOGC(inlog.Debug, log << "ACK: sndbuf buffer empty; not removing anything"); } + else if (!valid_sndbuf_revoke) + { + HLOGC(inlog.Debug, log << "ACK: sndbuf excessive removal attempt; clearing buffer"); + m_pSndBuffer->clear(); + } + else + { +#if ENABLE_BONDING + if (gkeeper.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); + // 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); + // acknowledge the sending buffer (remove data that predate 'ack') + HLOGC(inlog.Debug, log << "ACK: sndbuf: removing " << offset << "/" << bufsize << " packets"); + m_pSndBuffer->ackData(offset); + } // acknowledde any waiting epolls to write uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); @@ -8242,6 +8334,8 @@ void srt::CUDT::updateStateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seq } #endif + HLOGC(inlog.Debug, log << "ACK: kicking the send schedule/cond"); + // insert this socket to snd list if it is not on the list yet const steady_clock::time_point currtime = steady_clock::now(); m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE, currtime); @@ -8257,6 +8351,8 @@ void srt::CUDT::updateStateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seq m_stats.m_sndDurationTotal += count_microseconds(currtime - m_stats.sndDurationCounter); m_stats.sndDurationCounter = currtime; leaveCS(m_StatsLock); + + return valid_sndbuf_revoke; } void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point& currtime) @@ -8279,7 +8375,7 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ const bool isLiteAck = ctrlpkt.getLength() == (size_t)SEND_LITE_ACK; HLOGC(inlog.Debug, log << CONID() << "ACK covers: " << m_iSndLastDataAck << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck - << "]" << (isLiteAck ? "[LITE]" : "[FULL]")); + << "]" << (isLiteAck ? "[LITE]" : "[FULL]") << " last-sent=%" << m_iSndCurrSeqNo); // last_sent_seqno is the value of m_iSndCurrSeqNo in general, // but for parallel-link groups (broadcast and balancing) this should @@ -8287,7 +8383,13 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ // latest sequence sent for the group, no matter through which link // it was sent. int32_t last_sent_seqno; - updateStateOnACK(ackdata_seqno, (last_sent_seqno)); + if (!updateStateOnACK(ackdata_seqno, (last_sent_seqno))) + { + LOGC(inlog.Error, log << "ACK: IPE/EPE: %" << ackdata_seqno << " considered rogue. BREAKING."); + m_bBroken = true; + m_iBrokenCounter = 0; + return; + } // Process a lite ACK if (isLiteAck) @@ -8298,9 +8400,6 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ m_iFlowWindowSize = m_iFlowWindowSize - CSeqNo::seqoff(m_iSndLastAck, ackdata_seqno); m_iSndLastAck = ackdata_seqno; m_iSndMinFlightSpan = getFlightSpan(); - - // TODO: m_tsLastRspAckTime should be protected with m_RecvAckLock - // because the sendmsg2 may want to change it at the same time. m_tsLastRspAckTime = currtime; m_iReXmitCount = 1; // Reset re-transmit count since last ACK } @@ -8332,6 +8431,20 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ // Protect packet retransmission enterCS(m_RecvAckLock); + // XXX The problem is that this lock was intended to protect also + // the value of m_iSndCurrSeqNo from being modified in the meantime. + // In the current implementation we need the value of either this, or + // a similar field in the group data, which carries the latest possible + // sent sequence number. As this was turned into a variable last_sent_seqno + // this can be now modified in between. + // + // This might be fixed here by simply taking an "offline" value from the + // group, while taking the latest of this value from socket and group, + // this time under a lock. + + if (CSeqNo::seqcmp(last_sent_seqno, m_iSndCurrSeqNo) < 0) + last_sent_seqno = m_iSndCurrSeqNo; + // Check the validation of the ack if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(last_sent_seqno)) > 0) { @@ -9081,7 +9194,9 @@ void srt::CUDT::processCtrl(const CPacket &ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" - << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.m_iID); + << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) + << ") socket=@" << ctrlpkt.m_iID + << " arg=" << ctrlpkt.getAckSeqNo() << "/0x" << hex << ctrlpkt.getAckSeqNo()); switch (ctrlpkt.getType()) { @@ -9581,6 +9696,16 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // scheduling order and priorities. if (m_parent->m_GroupMemberData && m_parent->m_GroupMemberData->use_send_schedule) { + // Prevent the group from being deleted during the processing + CUDTUnited::GroupKeeper gk(uglobal(), m_parent); + + if (!gk.group) + { + // Caught too late - skip. + HLOGC(qslog.Debug, log << CONID() << "packData(sched): no more group"); + return false; + } + // If this socket is a group member of a group that uses the send scheduler, // do not extract packets from the existing resources, but instead pick up // the sequence from the scheduler container and extract that sequence from @@ -9590,7 +9715,7 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // from the sender buffer. Packet filter packets should be stored in a separate // buffer (XXX not implemented yet) and delivered in order. vector seqs; - if (!m_parent->m_GroupOf->getSendSchedule(m_parent->m_GroupMemberData, (seqs))) + if (!gk.group->getSendSchedule(m_parent->m_GroupMemberData, (seqs))) { HLOGP(qslog.Debug, "packData(sched): No scheduled packets yet, nothing to send"); return false; @@ -9643,7 +9768,7 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // In this case, as it was picked up and is going to be removed from // the schedule, we can either execute it right now, or somehow put back // to the schedule. - payload = m_parent->m_GroupOf->packLostData(this, (w_packet), ss.seq); + payload = gk.group->packLostData(this, (w_packet), ss.seq); if (payload == 0) { // If this happens, it means that there was a sequence found among @@ -9766,13 +9891,19 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime } } + // Just in case when the socket is about to be removed, quit before any + // data are reached. + // XXX check if the lock on GlobControlLock isn't required for this as well. + if (!m_bOpened) + return false; + // Note: the sending schedule could have been updated in the meantime, but not // by removing elements - only this function can remove elements from there, or // when closing a socket. So this will always cover these sequences that have been // extracted here above. HLOGC(qslog.Debug, log << "packData(sched): discard " << nremoved << "/" << seqs.size() << " scheduled events"); - m_parent->m_GroupOf->discardSendSchedule(m_parent->m_GroupMemberData, nremoved); + gk.group->discardSendSchedule(m_parent->m_GroupMemberData, nremoved); if (!payload) { diff --git a/srtcore/core.h b/srtcore/core.h index 56aec7b09..59aca10b8 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1053,7 +1053,7 @@ class CUDT /// whole group. /// /// @param ackdata_seqno sequence number of a data packet being acknowledged - void updateStateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seqno); + bool updateStateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seqno); /// Pack a packet from a list of lost packets. /// @param packet [in, out] a packet structure to fill diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 46c9da00a..a52768733 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -273,10 +273,9 @@ void CUDTGroup::createBuffers(int32_t isn, const time_point& tsbpd_start_time, i } /// Update the internal state after a single link has been switched to RUNNING state. +// [[using locked(m_GroupLock)]] void CUDTGroup::updateRcvRunningState() { - ScopedLock lk (m_GroupLock); - size_t nrunning; for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { @@ -287,6 +286,7 @@ void CUDTGroup::updateRcvRunningState() m_Group.set_number_running(nrunning); } +// [[using locked(m_GroupLock)]] void CUDTGroup::updateErasedLink() { // When a link has been erased, reset the tracing data @@ -5719,14 +5719,8 @@ bool CUDTGroup::updateSendPacketLoss(bool use_send_sched, const std::vector< std return true; } -bool CUDTGroup::updateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seqno) +void CUDTGroup::updateOnACK(int32_t ackdata_seqno) { - w_last_sent_seqno = getSentSeq(); - /* - if (CSeqNo::seqcmp(ackdata_seqno, w_last_sent_seqno) > 0) - return false; - */ - ScopedLock guard(m_LossAckLock); if (CSeqNo::seqcmp(m_SndLastDataAck, ackdata_seqno) < 0) { @@ -5734,8 +5728,6 @@ bool CUDTGroup::updateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seqno) m_pSndLossList->removeUpTo(CSeqNo::decseq(ackdata_seqno)); m_SndLastDataAck = ackdata_seqno; } - - return true; } // This is almost a copy of the CUDT::packLostData except that: @@ -5885,6 +5877,7 @@ SRT_ATR_NODISCARD bool CUDTGroup::getSendSchedule(SocketData* d, vector int(d->send_schedule.size())) { LOGC(gmlog.Error, log << "grp/discardSendSchedule: IPE: size " << ndiscard << " is out of range of " << d->send_schedule.size() << " (fallback: clear all)"); diff --git a/srtcore/group.h b/srtcore/group.h index 7b7e100f5..43858892b 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -477,8 +477,12 @@ class CUDTGroup sync::atomic m_zLongestDistance; atomic_duration m_tdLongestDistance; public: + SRT_ATTR_REQUIRES(m_GroupLock) void updateRcvRunningState(); + + SRT_ATTR_REQUIRES(m_GroupLock) void updateErasedLink(); + void updateInterlinkDistance(); private: @@ -958,7 +962,7 @@ class CUDTGroup SRT_ATR_NODISCARD bool getSendSchedule(SocketData* d, std::vector& w_sched); void discardSendSchedule(SocketData* d, int ndiscard); - bool updateOnACK(int32_t ackdata_seqno, int32_t& w_last_sent_seqno); + void updateOnACK(int32_t ackdata_seqno); int packLostData(CUDT* core, CPacket& w_packet, int32_t exp_seq); time_point getPktTsbPdTime(uint32_t usPktTimestamp) const From a826e3c933dda5e77a014ac3828862ea06e7703c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 16 Dec 2022 16:13:03 +0100 Subject: [PATCH 38/62] Trial fix for the rogue ACK in backup groups --- srtcore/core.cpp | 114 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 19 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 1ecf8859f..7ce6ee9cf 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -7806,10 +7806,99 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) if (!getFirstNoncontSequence((ack), (reason))) return nbsent; + // Lock the group existence until this function ends. This will be useful + // also on other places. +#if ENABLE_BONDING + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); + + // bonding : group buffering if member + const bool group_buffering = gkeeper.group; + + if (group_buffering) + { + // NOTE: in case of a Backup-type group, IDLE links are considered to never + // sending any packets, hence nothing is to be acknowledged. The problem is + // that normally the buffering activities were interconnected with ACK-ing, + // however in case of group reception the common receiver buffering causes + // that the fact of having received a packet IN THE BUFFER doesn't simultaneously + // mean that the packet was received OVER THIS LINK. + + // So, first, check if this link was IDLE. For IDLE links, ACKs should not + // be sent at all. + // + // There is one more small problem though. When a link is being silenced, + // then it should turn from RUNNING to IDLE, however the recognition of + // this fact is only possible at the moment when the first KEEPALIVE arrives + // after the data stop coming. The problem of the wrong ACK could occur + // just as well during this period. + // + // Therefore the best way is, beside rejecting ACK on non-RUNNING + // links, in case of RUNNING state, additionally there should be + // checked if the last sent sequence exceeds the current last ACK + // received. If not, also no ACK should be sent, even if the + // noncontiguous sequence was shifted. + + if (gkeeper.group->type() == SRT_GTYPE_BACKUP) + { + // Lock the GlobControlLock to avoid consideration for a broken link. + ScopedLock glk (uglobal().m_GlobControlLock); + + groups::SocketData* pd = m_parent->m_GroupMemberData; + if (!m_bOpened || m_bClosing || !pd) + return 0; + + if (pd->rcvstate != SRT_GST_RUNNING) + { + // Do not send ACK for non-RUNNING links + return 0; + } + + // So, check now if anything has arrived + // over THIS LINK since the last ACK. + // NOTE: this is still being done for the + // backup group only because only in case of + // this group there can happen an immediate + // stop of the transmission on one of the links + // ("silencing"), of which the receiver has no + // idea. In broadcast and balancing groups you + // can safely send ACK basing on the latest + // contiguous sequence in the buffer because all + // links are supposed to be active and deliver + // packets. + + // Note also that the IDLE state on the receiver + // side is only notified upon reception of KEEPALIVE. + // Until then it's simply a link that doesn't deliver + // data. + int32_t pe_recv_seq = CSeqNo::incseq(m_iRcvCurrSeqNo); + if (CSeqNo::seqcmp(ack, pe_recv_seq) > 0) + { + if (CSeqNo::seqcmp(m_iRcvLastAck, pe_recv_seq) >= 0) + { + HLOGC(xtlog.Debug, log << CONID() << "sendCtrlAck: grp/BACKUP: buf-ACK %" << ack + << " exceeds last-rcv %" << m_iRcvCurrSeqNo << " == last ack %" << m_iRcvLastAck + << " - NOT SENDING (considered pending IDLE)"); + return 0; + } + + HLOGC(xtlog.Debug, log << CONID() << "sendCtrlAck: grp/BACKUP: buf-ACK %" << ack + << "exceeds last-rcv %" << m_iRcvCurrSeqNo << " %> last ack %" << m_iRcvLastAck + << " - sending FIXED ack %" << pe_recv_seq); + ack = pe_recv_seq; + } + } + } + +#else + // no bonding : no group buffering + const bool group_buffering = false; +#endif + + if (m_iRcvLastAckAck == ack) { HLOGC(xtlog.Debug, - log << CONID() << "sendCtrl(UMSG_ACK): last ACK %" << ack << "(" << reason << ") == last ACKACK"); + log << CONID() << "sendCtrlAck: last ACK %" << ack << "(" << reason << ") == last ACKACK"); return nbsent; } @@ -7820,23 +7909,10 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) ctrlpkt.pack(UMSG_ACK, NULL, &ack, size); ctrlpkt.m_iID = m_PeerID; nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); - DebugAck(CONID() + "sendCtrl(lite): ", local_prevack, ack); + DebugAck(CONID() + "sendCtrlAck(lite): ", local_prevack, ack); return nbsent; } - // Lock the group existence until this function ends. This will be useful - // also on other places. -#if ENABLE_BONDING - CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); - - // bonding : group buffering if member - const bool group_buffering = gkeeper.group; - -#else - // no bonding : no group buffering - const bool group_buffering = false; -#endif - int avail_receiver_buffer_size = 0; #if ENABLE_BONDING if (group_buffering) @@ -8024,14 +8100,14 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) (microseconds_from(m_iSRTT + 4 * m_iRTTVar))) { HLOGC(xtlog.Debug, - log << CONID() << "sendCtrl(UMSG_ACK): ACK %" << ack << " just sent - too early to repeat"); + log << CONID() << "sendCtrlAck: ACK %" << ack << " just sent - too early to repeat"); return nbsent; } } else { // Not possible (m_iRcvCurrSeqNo+1 <% m_iRcvLastAck ?) - LOGC(xtlog.Error, log << CONID()<< "sendCtrl(UMSG_ACK): IPE: curr(" << reason << ") %" << ack + LOGC(xtlog.Error, log << CONID()<< "sendCtrlAck: IPE: curr(" << reason << ") %" << ack << " <% last %" << m_iRcvLastAck); return nbsent; } @@ -8103,7 +8179,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) ctrlpkt.m_iID = m_PeerID; setPacketTS(ctrlpkt, steady_clock::now()); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); - DebugAck(CONID() + "sendCtrl(UMSG_ACK): ", local_prevack, ack); + DebugAck(CONID() + "sendCtrlAck: ", local_prevack, ack); m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck); @@ -8113,7 +8189,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) } else { - HLOGC(xtlog.Debug, log << CONID() << "sendCtrl(UMSG_ACK): " << "ACK %" << m_iRcvLastAck + HLOGC(xtlog.Debug, log << CONID() << "sendCtrlAck: " << "ACK %" << m_iRcvLastAck << " <=% ACKACK %" << m_iRcvLastAckAck << " - NOT SENDING ACK"); } From b1168380a4c776564e527e6ee2e81020e0625dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 16 Dec 2022 17:40:28 +0100 Subject: [PATCH 39/62] Some cosmetic log fixes --- srtcore/core.cpp | 6 +++--- srtcore/group.cpp | 2 ++ srtcore/group.h | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 7ce6ee9cf..292ec4808 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -7778,7 +7778,7 @@ bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) else w_log_reason = "expected next"; } - HLOGC(xtlog.Debug, log << "NONCONT-SEQUENCE: " << w_log_reason << " %" << w_seq); + HLOGC(xtlog.Debug, log << CONID() << "NONCONT-SEQUENCE: " << w_log_reason << " %" << w_seq); return true; } @@ -7898,7 +7898,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) if (m_iRcvLastAckAck == ack) { HLOGC(xtlog.Debug, - log << CONID() << "sendCtrlAck: last ACK %" << ack << "(" << reason << ") == last ACKACK"); + log << CONID() << "sendCtrlAck: last ACK %" << ack << "(" << reason << ") == last ACKACK; NOT sending."); return nbsent; } @@ -8047,7 +8047,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) rdcc.notify_one(); } // acknowledge any waiting epolls to read - // fix SRT_EPOLL_IN event loss but rcvbuffer still have data: + // fix SRT_EPOLL_IN event loss but rcvbuffer still have data: // 1. user call receive/receivemessage(about line number:6482) // 2. after read/receive, if rcvbuffer is empty, will set SRT_EPOLL_IN event to false // 3. but if we do not do some lock work here, will cause some sync problems between threads: diff --git a/srtcore/group.cpp b/srtcore/group.cpp index a52768733..a41e991e1 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -1359,6 +1359,8 @@ bool CUDTGroup::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) else w_log_reason = "last received"; + HLOGC(xtlog.Debug, log << CONID() << "NONCONT-SEQUENCE: " << w_log_reason << " %" << w_seq); + return true; } diff --git a/srtcore/group.h b/srtcore/group.h index 43858892b..fb04fdd08 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -909,7 +909,7 @@ class CUDTGroup { #if ENABLE_LOGGING std::ostringstream os; - os << "@" << m_GroupID << ":"; + os << "$" << m_GroupID << ":"; return os.str(); #else return ""; From dc2dcdf7eeab34f5ad4ec9660991fe6bd8a58b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Tue, 18 Jul 2023 11:14:57 +0200 Subject: [PATCH 40/62] Updated lock info in the comment --- srtcore/core.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 5f5cefa9d..904ed4440 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -6543,8 +6543,12 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) // simply return the size, pretending that it has been sent. // NOTE: it's assumed that if this is a group member, then - // an attempt to call srt_sendmsg2 has been rejected, and so - // the pktseq field has been set by the internal group sender function. + // an attempt to call srt_sendmsg2 for a single (also member) socket + // has been rejected, and so the pktseq field has been set by the + // internal group sender function. + + // NOTE 2: it is assumed that if m_GroupOf is not NULL this means + // that this function is called under m_parent->m_GroupOf->m_GroupLock locked. if (m_parent->m_GroupOf) { if ( w_mctrl.pktseq != SRT_SEQNO_NONE From 4bb7b47998c5e1c82b8649b95ba2150e5bd073e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Tue, 19 Sep 2023 10:18:04 +0200 Subject: [PATCH 41/62] Added mutex spec to a function --- srtcore/core.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/srtcore/core.h b/srtcore/core.h index 6ae9c7e89..538df167c 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -668,6 +668,8 @@ class CUDT /// the receiver fresh loss list. void unlose(const CPacket& oldpacket); void dropFromLossLists(int32_t from, int32_t to); + + SRT_ATTR_EXCLUDES(m_RcvBufferLock) bool getFirstNoncontSequence(int32_t& w_seq, std::string& w_log_reason); SRT_ATTR_EXCLUDES(m_ConnectionLock) From d1798784a1e6573461a9caa493603eba63ba2fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Tue, 19 Sep 2023 13:05:27 +0200 Subject: [PATCH 42/62] Added more thread check entries --- srtcore/api.h | 2 ++ srtcore/core.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/srtcore/api.h b/srtcore/api.h index 9ba77d23a..9a5c6081c 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -123,6 +123,8 @@ class CUDTSocket void construct(); + // XXX Controversial as to whether it should be guarded by this lock. + // It is used in many places without the lock, and it is also atomic. SRT_ATTR_GUARDED_BY(m_ControlLock) sync::atomic m_Status; //< current socket state diff --git a/srtcore/core.h b/srtcore/core.h index 538df167c..b8a24032a 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -318,6 +318,7 @@ class CUDT #endif int32_t rcvSeqNo() const { return m_iRcvCurrSeqNo; } + SRT_ATTR_REQUIRES(m_RecvAckLock) int flowWindowSize() const { return m_iFlowWindowSize; } int32_t deliveryRate() const { return m_iDeliveryRate; } int bandwidth() const { return m_iBandwidth; } @@ -365,6 +366,7 @@ class CUDT /// Returns the number of packets in flight (sent, but not yet acknowledged). /// @returns The number of packets in flight belonging to the interval [0; ...) + SRT_ATTR_REQUIRES(m_RecvAckLock) int32_t getFlightSpan() const { return getFlightSpan(m_iSndLastAck, m_iSndCurrSeqNo); From 8ad69ed81243466e633bc02960ef0f87cb786da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 15 Feb 2024 13:38:20 +0100 Subject: [PATCH 43/62] Fixed a suggested uninitialized variable --- srtcore/buffer_rcv.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 488b73f35..235909e90 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -364,6 +364,11 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) avail_packet = &packetAt(m_iDropPos); avail_range = 1; } + else + { + avail_packet = NULL; + avail_range = 0; + } IF_RCVBUF_DEBUG(scoped_log.ss << " returns 0 (OK)"); IF_HEAVY_LOGGING(debugShowState((debug_source + " ok").c_str())); From bbced79792597a1d11f9051be9ac157658878fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 16 Feb 2024 09:19:47 +0100 Subject: [PATCH 44/62] Renamed eclipsed variable --- srtcore/buffer_rcv.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 235909e90..a7693a839 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -1495,7 +1495,7 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) } // Start position - int pos = incPos(m_iStartPos, offset); + int frompos = incPos(m_iStartPos, offset); // Ok; likely we should stand at the m_iEndPos position. // If this given position is earlier than this, then @@ -1506,7 +1506,7 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) int ret_off = m_iMaxPosOff; int end_off = offPos(m_iStartPos, m_iEndPos); - if (pos < end_off) + if (frompos < end_off) { // If m_iEndPos has such a value, then there are // no loss packets at all. @@ -1560,7 +1560,7 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) } // Fallback - this should be impossible, so issue a log. - LOGC(rbuflog.Error, log << "IPE: empty cell pos=" << pos << " %" << CSeqNo::incseq(m_iStartSeqNo, ret_off) << " not followed by any valid cell"); + LOGC(rbuflog.Error, log << "IPE: empty cell pos=" << frompos << " %" << CSeqNo::incseq(m_iStartSeqNo, ret_off) << " not followed by any valid cell"); // Return this in the last resort - this could only be a situation when // a packet has somehow disappeared, but it contains empty cells up to the From 5136ca2c3eb6ffb8c6c92fe55cc154cb2513151d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 16 Feb 2024 10:32:39 +0100 Subject: [PATCH 45/62] Added doxy description for some functions. Applied NonOrder in names for out-of-order concerns --- srtcore/buffer_rcv.cpp | 100 ++++++++++++++++++----------------------- srtcore/buffer_rcv.h | 59 +++++++++++++++++------- srtcore/utilities.h | 2 +- 3 files changed, 89 insertions(+), 72 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index a7693a839..831840967 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -126,8 +126,8 @@ CRcvBuffer::CRcvBuffer(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool b , m_iFirstNonreadPos(0) , m_iMaxPosOff(0) , m_iNotch(0) - , m_numRandomPackets(0) - , m_iFirstRandomMsgPos(-1) + , m_numNonOrderPackets(0) + , m_iFirstNonOrderMsgPos(-1) , m_bPeerRexmitFlag(true) , m_bMessageAPI(bMessageAPI) , m_iBytesCount(0) @@ -337,8 +337,8 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) // With TSBPD enabled packets are always assumed in order (the flag is ignored). if (!m_tsbpd.isEnabled() && m_bMessageAPI && !unit->m_Packet.getMsgOrderFlag()) { - ++m_numRandomPackets; - onInsertNotInOrderPacket(newpktpos); + ++m_numNonOrderPackets; + onInsertNonOrderPacket(newpktpos); } updateNonreadPos(); @@ -350,13 +350,13 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) avail_packet = &packetAt(m_iStartPos); avail_range = offPos(m_iStartPos, m_iEndPos); } - else if (!m_tsbpd.isEnabled() && m_iFirstRandomMsgPos != -1) + else if (!m_tsbpd.isEnabled() && m_iFirstNonOrderMsgPos != -1) { // In case when TSBPD is off, we take into account the message mode // where messages may potentially span for multiple packets, therefore // the only "next deliverable" is the first complete message that satisfies // the order requirement. - avail_packet = &packetAt(m_iFirstRandomMsgPos); + avail_packet = &packetAt(m_iFirstNonOrderMsgPos); avail_range = 1; } else if (m_iDropPos != m_iEndPos) @@ -379,18 +379,6 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) return InsertInfo(InsertInfo::INSERTED); // No packet candidate (NOTE: impossible in live mode) } -// This function should be called after having m_iEndPos -// has somehow be set to position of a non-empty cell. -// This can happen by two reasons: -// - the cell has been filled by incoming packet -// - the value has been reset due to shifted m_iStartPos -// This means that you have to search for a new gap and -// update the m_iEndPos and m_iDropPos fields, or set them -// both to the end of range. -// -// prev_max_pos should be the position represented by m_iMaxPosOff. -// Passed because it is already calculated in insert(), otherwise -// it would have to be calculated here again. void CRcvBuffer::updateGapInfo(int prev_max_pos) { int pos = m_iEndPos; @@ -474,7 +462,7 @@ int CRcvBuffer::dropUpTo(int32_t seqno) updateNonreadPos(); } if (!m_tsbpd.isEnabled() && m_bMessageAPI) - updateFirstReadableRandom(); + updateFirstReadableNonOrder(); IF_HEAVY_LOGGING(debugShowState(("drop %" + Sprint(seqno)).c_str())); return iDropCnt; @@ -613,9 +601,9 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro } if (!m_tsbpd.isEnabled() && m_bMessageAPI) { - if (!checkFirstReadableRandom()) - m_iFirstRandomMsgPos = -1; - updateFirstReadableRandom(); + if (!checkFirstReadableNonOrder()) + m_iFirstNonOrderMsgPos = -1; + updateFirstReadableNonOrder(); } IF_HEAVY_LOGGING(debugShowState(("dropmsg off %" + Sprint(seqnolo)).c_str())); @@ -645,14 +633,14 @@ bool CRcvBuffer::getContiguousEnd(int32_t& w_seq) const int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair* pw_seqrange) { const bool canReadInOrder = hasReadableInorderPkts(); - if (!canReadInOrder && m_iFirstRandomMsgPos < 0) + if (!canReadInOrder && m_iFirstNonOrderMsgPos < 0) { LOGC(rbuflog.Warn, log << "CRcvBuffer.readMessage(): nothing to read. Ignored isRcvDataReady() result?"); return 0; } //const bool canReadInOrder = m_iFirstNonreadPos != m_iStartPos; - const int readPos = canReadInOrder ? m_iStartPos : m_iFirstRandomMsgPos; + const int readPos = canReadInOrder ? m_iStartPos : m_iFirstNonOrderMsgPos; const bool isReadingFromStart = (readPos == m_iStartPos); // Indicates if the m_iStartPos can be changed IF_RCVBUF_DEBUG(ScopedLog scoped_log); @@ -696,8 +684,8 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair 0 && m_iFirstRandomMsgPos != -1); + return hasReadableInorderPkts() || (m_numNonOrderPackets > 0 && m_iFirstNonOrderMsgPos != -1); } int CRcvBuffer::getRcvDataSize() const @@ -1028,8 +1016,8 @@ bool CRcvBuffer::isRcvDataReady(time_point time_now) const if (haveInorderPackets) return true; - SRT_ASSERT((!m_bMessageAPI && m_numRandomPackets == 0) || m_bMessageAPI); - return (m_numRandomPackets > 0 && m_iFirstRandomMsgPos != -1); + SRT_ASSERT((!m_bMessageAPI && m_numNonOrderPackets == 0) || m_bMessageAPI); + return (m_numNonOrderPackets > 0 && m_iFirstNonOrderMsgPos != -1); } if (!haveInorderPackets) @@ -1053,11 +1041,11 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstReadablePacketInfo(time_point time_no const PacketInfo info = {packet.getSeqNo(), false, time_point()}; return info; } - SRT_ASSERT((!m_bMessageAPI && m_numRandomPackets == 0) || m_bMessageAPI); - if (m_iFirstRandomMsgPos >= 0) + SRT_ASSERT((!m_bMessageAPI && m_numNonOrderPackets == 0) || m_bMessageAPI); + if (m_iFirstNonOrderMsgPos >= 0) { - SRT_ASSERT(m_numRandomPackets > 0); - const CPacket& packet = packetAt(m_iFirstRandomMsgPos); + SRT_ASSERT(m_numNonOrderPackets > 0); + const CPacket& packet = packetAt(m_iFirstNonOrderMsgPos); const PacketInfo info = {packet.getSeqNo(), true, time_point()}; return info; } @@ -1107,9 +1095,9 @@ bool CRcvBuffer::dropUnitInPos(int pos) } else if (m_bMessageAPI && !packetAt(pos).getMsgOrderFlag()) { - --m_numRandomPackets; - if (pos == m_iFirstRandomMsgPos) - m_iFirstRandomMsgPos = -1; + --m_numNonOrderPackets; + if (pos == m_iFirstNonOrderMsgPos) + m_iFirstNonOrderMsgPos = -1; } releaseUnitInPos(pos); return true; @@ -1185,9 +1173,9 @@ int CRcvBuffer::findLastMessagePkt() return -1; } -void CRcvBuffer::onInsertNotInOrderPacket(int insertPos) +void CRcvBuffer::onInsertNonOrderPacket(int insertPos) { - if (m_numRandomPackets == 0) + if (m_numNonOrderPackets == 0) return; // If the following condition is true, there is already a packet, @@ -1196,7 +1184,7 @@ void CRcvBuffer::onInsertNotInOrderPacket(int insertPos) // // There might happen that the packet being added precedes the previously found one. // However, it is allowed to re bead out of order, so no need to update the position. - if (m_iFirstRandomMsgPos >= 0) + if (m_iFirstNonOrderMsgPos >= 0) return; // Just a sanity check. This function is called when a new packet is added. @@ -1209,34 +1197,34 @@ void CRcvBuffer::onInsertNotInOrderPacket(int insertPos) //if ((boundary & PB_FIRST) && (boundary & PB_LAST)) //{ // // This packet can be read out of order - // m_iFirstRandomMsgPos = insertPos; + // m_iFirstNonOrderMsgPos = insertPos; // return; //} const int msgNo = pkt.getMsgSeq(m_bPeerRexmitFlag); // First check last packet, because it is expected to be received last. - const bool hasLast = (boundary & PB_LAST) || (-1 < scanNotInOrderMessageRight(insertPos, msgNo)); + const bool hasLast = (boundary & PB_LAST) || (-1 < scanNonOrderMessageRight(insertPos, msgNo)); if (!hasLast) return; const int firstPktPos = (boundary & PB_FIRST) ? insertPos - : scanNotInOrderMessageLeft(insertPos, msgNo); + : scanNonOrderMessageLeft(insertPos, msgNo); if (firstPktPos < 0) return; - m_iFirstRandomMsgPos = firstPktPos; + m_iFirstNonOrderMsgPos = firstPktPos; return; } -bool CRcvBuffer::checkFirstReadableRandom() +bool CRcvBuffer::checkFirstReadableNonOrder() { - if (m_numRandomPackets <= 0 || m_iFirstRandomMsgPos < 0 || m_iMaxPosOff == 0) + if (m_numNonOrderPackets <= 0 || m_iFirstNonOrderMsgPos < 0 || m_iMaxPosOff == 0) return false; const int endPos = incPos(m_iStartPos, m_iMaxPosOff); int msgno = -1; - for (int pos = m_iFirstRandomMsgPos; pos != endPos; pos = incPos(pos)) + for (int pos = m_iFirstNonOrderMsgPos; pos != endPos; pos = incPos(pos)) { if (!m_entries[pos].pUnit) return false; @@ -1257,16 +1245,16 @@ bool CRcvBuffer::checkFirstReadableRandom() return false; } -void CRcvBuffer::updateFirstReadableRandom() +void CRcvBuffer::updateFirstReadableNonOrder() { - if (hasReadableInorderPkts() || m_numRandomPackets <= 0 || m_iFirstRandomMsgPos >= 0) + if (hasReadableInorderPkts() || m_numNonOrderPackets <= 0 || m_iFirstNonOrderMsgPos >= 0) return; if (m_iMaxPosOff == 0) return; // TODO: unused variable outOfOrderPktsRemain? - int outOfOrderPktsRemain = (int) m_numRandomPackets; + int outOfOrderPktsRemain = (int) m_numNonOrderPackets; // Search further packets to the right. // First check if there are packets to the right. @@ -1309,7 +1297,7 @@ void CRcvBuffer::updateFirstReadableRandom() if (boundary & PB_LAST) { - m_iFirstRandomMsgPos = posFirst; + m_iFirstNonOrderMsgPos = posFirst; return; } @@ -1320,7 +1308,7 @@ void CRcvBuffer::updateFirstReadableRandom() return; } -int CRcvBuffer::scanNotInOrderMessageRight(const int startPos, int msgNo) const +int CRcvBuffer::scanNonOrderMessageRight(const int startPos, int msgNo) const { // Search further packets to the right. // First check if there are packets to the right. @@ -1351,7 +1339,7 @@ int CRcvBuffer::scanNotInOrderMessageRight(const int startPos, int msgNo) const return -1; } -int CRcvBuffer::scanNotInOrderMessageLeft(const int startPos, int msgNo) const +int CRcvBuffer::scanNonOrderMessageLeft(const int startPos, int msgNo) const { // Search preceding packets to the left. // First check if there are packets to the left. diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 22393dbcd..70099d355 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -244,16 +244,45 @@ class CRcvBuffer }; - /// Insert a unit into the buffer. - /// Similar to CRcvBuffer::addData(CUnit* unit, int offset) + /// Inserts the unit with the data packet into the receiver buffer. + /// The result inform about the situation with the packet attempted + /// to be inserted and the readability of the buffer. /// - /// @param [in] unit pointer to a data unit containing new packet - /// @param [in] offset offset from last ACK point. + /// @param [PASS] unit The unit that should be placed in the buffer + /// + /// @return The InsertInfo structure where: + /// * result: the result of insertion, which is: + /// * INSERTED: successfully placed in the buffer + /// * REDUNDANT: not placed, the packet is already there + /// * BELATED: not placed, its sequence is in the past + /// * DISCREPANCY: not placed, the sequence is far future or OOTB + /// * first_seq: the earliest sequence number now avail for reading + /// * avail_range: how many packets are available for reading (1 if unknown) + /// * first_time: the play time of the earliest read-available packet + /// If there is no available packet for reading, first_seq == SRT_SEQNO_NONE. /// - /// @return 0 on success, -1 if packet is already in buffer, -2 if packet is before m_iStartSeqNo. - /// -3 if a packet is offset is ahead the buffer capacity. - // TODO: Previously '-2' also meant 'already acknowledged'. Check usage of this value. InsertInfo insert(CUnit* unit); + + /// Update the values of `m_iEndPos` and `m_iDropPos` in + /// case when `m_iEndPos` was updated to a position of a + /// nonempty cell. + /// + /// This function should be called after having m_iEndPos + /// has somehow be set to position of a non-empty cell. + /// This can happen by two reasons: + /// + /// - the cell has been filled by incoming packet + /// - the value has been reset due to shifted m_iStartPos + /// + /// This means that you have to search for a new gap and + /// update the m_iEndPos and m_iDropPos fields, or set them + /// both to the end of range if there are no loss gaps. + /// + /// The @a prev_max_pos parameter is passed here because it is already + /// calculated in insert(), otherwise it would have to be calculated here again. + /// + /// @param prev_max_pos buffer position represented by `m_iMaxPosOff` + /// void updateGapInfo(int prev_max_pos); /// Drop packets in the receiver buffer from the current position up to the seqno (excluding seqno). @@ -495,12 +524,12 @@ class CRcvBuffer int findLastMessagePkt(); /// Scan for availability of out of order packets. - void onInsertNotInOrderPacket(int insertpos); - // Check if m_iFirstRandomMsgPos is still readable. - bool checkFirstReadableRandom(); - void updateFirstReadableRandom(); - int scanNotInOrderMessageRight(int startPos, int msgNo) const; - int scanNotInOrderMessageLeft(int startPos, int msgNo) const; + void onInsertNonOrderPacket(int insertpos); + // Check if m_iFirstNonOrderMsgPos is still readable. + bool checkFirstReadableNonOrder(); + void updateFirstReadableNonOrder(); + int scanNonOrderMessageRight(int startPos, int msgNo) const; + int scanNonOrderMessageLeft(int startPos, int msgNo) const; typedef bool copy_to_dst_f(char* data, int len, int dst_offset, void* arg); @@ -566,12 +595,12 @@ class CRcvBuffer int m_iMaxPosOff; // the furthest data position int m_iNotch; // index of the first byte to read in the first ready-to-read packet (used in file/stream mode) - size_t m_numRandomPackets; // The number of stored packets with "inorder" flag set to false + size_t m_numNonOrderPackets; // The number of stored packets with "inorder" flag set to false /// Points to the first packet of a message that has out-of-order flag /// and is complete (all packets from first to last are in the buffer). /// If there is no such message in the buffer, it contains -1. - int m_iFirstRandomMsgPos; + int m_iFirstNonOrderMsgPos; bool m_bPeerRexmitFlag; // Needed to read message number correctly const bool m_bMessageAPI; // Operation mode flag: message or stream. diff --git a/srtcore/utilities.h b/srtcore/utilities.h index 258a2fdc8..3d9cf1c09 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -682,7 +682,7 @@ class UniquePtr: public std::auto_ptr bool operator==(const element_type* two) const { return get() == two; } bool operator!=(const element_type* two) const { return get() != two; } - operator bool () { return 0!= get(); } + operator bool () const { return 0!= get(); } }; // A primitive one-argument versions of Sprint and Printable From f6e0271c72a16dd12942651eae4cec28a39f586c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 16 Feb 2024 11:28:24 +0100 Subject: [PATCH 46/62] Removed wrong fix. Fixed C++11 style initialization of PacketInfo --- srtcore/buffer_rcv.cpp | 26 ++++++++++++++++++++------ srtcore/utilities.h | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 831840967..566854984 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -986,20 +986,34 @@ int CRcvBuffer::getRcvDataSize(int& bytes, int& timespan) const CRcvBuffer::PacketInfo CRcvBuffer::getFirstValidPacketInfo() const { - // Check the state of the very first packet first + // Default: no packet available. + PacketInfo pi = { SRT_SEQNO_NONE, false, time_point() }; + + const CPacket* pkt = NULL; + + // Very first packet available with no gap. if (m_entries[m_iStartPos].status == EntryState_Avail) { SRT_ASSERT(m_entries[m_iStartPos].pUnit); - return { m_iStartSeqNo, false /*no gap*/, getPktTsbPdTime(packetAt(m_iStartPos).getMsgTimeStamp()) }; + pkt = &packetAt(m_iStartPos); } // If not, get the information from the drop - if (m_iDropPos != m_iEndPos) + else if (m_iDropPos != m_iEndPos) + { + SRT_ASSERT(m_entries[m_iDropPos].pUnit); + pkt = &packetAt(m_iDropPos); + pi.seq_gap = true; // Available, but after a drop. + } + else { - const CPacket& pkt = packetAt(m_iDropPos); - return { pkt.getSeqNo(), true, getPktTsbPdTime(pkt.getMsgTimeStamp()) }; + // If none of them point to a valid packet, + // there is no packet available; + return pi; } - return { SRT_SEQNO_NONE, false, time_point() }; + pi.seqno = pkt->getSeqNo(); + pi.tsbpd_time = getPktTsbPdTime(pkt->getMsgTimeStamp()); + return pi; } std::pair CRcvBuffer::getAvailablePacketsRange() const diff --git a/srtcore/utilities.h b/srtcore/utilities.h index 3d9cf1c09..258a2fdc8 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -682,7 +682,7 @@ class UniquePtr: public std::auto_ptr bool operator==(const element_type* two) const { return get() == two; } bool operator!=(const element_type* two) const { return get() != two; } - operator bool () const { return 0!= get(); } + operator bool () { return 0!= get(); } }; // A primitive one-argument versions of Sprint and Printable From 072a8c482ec9a8963a9ca0efa0a4ac2ff0df9688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Fri, 16 Feb 2024 16:29:55 +0100 Subject: [PATCH 47/62] Refax: CRcvBuffer extracted some parts of insert() to separate functions --- srtcore/buffer_rcv.cpp | 305 ++++++++++++++++++++++------------------- srtcore/buffer_rcv.h | 6 + 2 files changed, 167 insertions(+), 144 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 566854984..97b76d755 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -144,7 +144,7 @@ CRcvBuffer::~CRcvBuffer() { if (!it->pUnit) continue; - + m_pUnitQueue->makeUnitFree(it->pUnit); it->pUnit = NULL; } @@ -167,9 +167,6 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) IF_RCVBUF_DEBUG(scoped_log.ss << " msgno " << unit->m_Packet.getMsgSeq(m_bPeerRexmitFlag)); IF_RCVBUF_DEBUG(scoped_log.ss << " m_iStartSeqNo " << m_iStartSeqNo << " offset " << offset); - int32_t avail_seq; - int avail_range; - if (offset < 0) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -2"); @@ -181,31 +178,12 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -3"); - // Calculation done for the sake of possible discrepancy - // in order to inform the caller what to do. - if (m_entries[m_iStartPos].status == EntryState_Avail) - { - avail_seq = packetAt(m_iStartPos).getSeqNo(); - avail_range = m_iEndPos - m_iStartPos; - } - else if (m_iDropPos == m_iEndPos) - { - avail_seq = SRT_SEQNO_NONE; - avail_range = 0; - } - else - { - avail_seq = packetAt(m_iDropPos).getSeqNo(); - - // We don't know how many packets follow it exactly, - // but in this case it doesn't matter. We know that - // at least one is there. - avail_range = 1; - } + InsertInfo ireport (InsertInfo::DISCREPANCY); + getAvailInfo((ireport)); IF_HEAVY_LOGGING(debugShowState((debug_source + " overflow").c_str())); - return InsertInfo(InsertInfo::DISCREPANCY, avail_seq, avail_range); + return ireport; } // TODO: Don't do assert here. Process this situation somehow. @@ -243,95 +221,10 @@ 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; + time_point earlier_time = updatePosInfo(unit, prev_max_off, newpktpos, extended_end); - int prev_max_pos = incPos(m_iStartPos, prev_max_off); - - // Update flags - // Case [A] - if (extended_end) - { - // THIS means that the buffer WAS CONTIGUOUS BEFORE. - if (m_iEndPos == prev_max_pos) - { - // THIS means that the new packet didn't CAUSE a gap - if (m_iMaxPosOff == prev_max_off + 1) - { - // This means that m_iEndPos now shifts by 1, - // and m_iDropPos must be shifted together with it, - // as there's no drop to point. - m_iEndPos = incPos(m_iStartPos, m_iMaxPosOff); - m_iDropPos = m_iEndPos; - } - else - { - // Otherwise we have a drop-after-gap candidate - // which is the currently inserted packet. - // Therefore m_iEndPos STAYS WHERE IT IS. - m_iDropPos = incPos(m_iStartPos, m_iMaxPosOff - 1); - } - } - } - // - // Since this place, every newpktpos is in the range - // between m_iEndPos (inclusive) and a position for m_iMaxPosOff. - - // Here you can use prev_max_pos as the position represented - // by m_iMaxPosOff, as if !extended_end, it was unchanged. - else if (newpktpos == m_iEndPos) - { - // Case [D]: inserted a packet at the first gap following the - // contiguous region. This makes a potential to extend the - // contiguous region and we need to find its end. - - // If insertion happened at the very first packet, it is the - // new earliest packet now. In any other situation under this - // condition there's some contiguous packet range preceding - // this position. - if (m_iEndPos == m_iStartPos) - { - earlier_time = getPktTsbPdTime(unit->m_Packet.getMsgTimeStamp()); - } - - updateGapInfo(prev_max_pos); - } - // XXX Not sure if that's the best performant comparison - // What is meant here is that newpktpos is between - // m_iEndPos and m_iDropPos, though we know it's after m_iEndPos. - // CONSIDER: make m_iDropPos rather m_iDropOff, this will make - // this comparison a simple subtraction. Note that offset will - // have to be updated on every shift of m_iStartPos. - else if (cmpPos(newpktpos, m_iDropPos) < 0) - { - // Case [C]: the newly inserted packet precedes the - // previous earliest delivery position after drop, - // that is, there is now a "better" after-drop delivery - // candidate. - - // New position updated a valid packet on an earlier - // position than the drop position was before, although still - // following a gap. - // - // We know it because if the position has filled a gap following - // a valid packet, this preceding valid packet would be pointed - // by m_iDropPos, or it would point to some earlier packet in a - // contiguous series of valid packets following a gap, hence - // the above condition wouldn't be satisfied. - m_iDropPos = newpktpos; - - // If there's an inserted packet BEFORE drop-pos (which makes it - // a new drop-pos), while the very first packet is absent (the - // below condition), it means we have a new earliest-available - // packet. Otherwise we would have only a newly updated drop - // position, but still following some earlier contiguous range - // of valid packets - so it's earlier than previous drop, but - // not earlier than the earliest packet. - if (m_iStartPos == m_iEndPos) - { - earlier_time = getPktTsbPdTime(unit->m_Packet.getMsgTimeStamp()); - } - } - // OTHERWISE: case [D] in which nothing is to be updated. + InsertInfo ireport (InsertInfo::INSERTED); + ireport.first_time = earlier_time; // If packet "in order" flag is zero, it can be read out of order. // With TSBPD enabled packets are always assumed in order (the flag is ignored). @@ -343,40 +236,164 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) updateNonreadPos(); - CPacket* avail_packet = NULL; - - if (m_entries[m_iStartPos].pUnit && m_entries[m_iStartPos].status == EntryState_Avail) - { - avail_packet = &packetAt(m_iStartPos); - avail_range = offPos(m_iStartPos, m_iEndPos); - } - else if (!m_tsbpd.isEnabled() && m_iFirstNonOrderMsgPos != -1) - { - // In case when TSBPD is off, we take into account the message mode - // where messages may potentially span for multiple packets, therefore - // the only "next deliverable" is the first complete message that satisfies - // the order requirement. - avail_packet = &packetAt(m_iFirstNonOrderMsgPos); - avail_range = 1; - } - else if (m_iDropPos != m_iEndPos) - { - avail_packet = &packetAt(m_iDropPos); - avail_range = 1; - } - else - { - avail_packet = NULL; - avail_range = 0; - } + // This updates only the first_seq and avail_range fields. + getAvailInfo((ireport)); IF_RCVBUF_DEBUG(scoped_log.ss << " returns 0 (OK)"); IF_HEAVY_LOGGING(debugShowState((debug_source + " ok").c_str())); - if (avail_packet) - return InsertInfo(InsertInfo::INSERTED, avail_packet->getSeqNo(), avail_range, earlier_time); - else - return InsertInfo(InsertInfo::INSERTED); // No packet candidate (NOTE: impossible in live mode) + return ireport; +} + +void CRcvBuffer::getAvailInfo(CRcvBuffer::InsertInfo& w_if) +{ + int fallback_pos = -1; + if (!m_tsbpd.isEnabled()) + { + // In case when TSBPD is off, we take into account the message mode + // where messages may potentially span for multiple packets, therefore + // the only "next deliverable" is the first complete message that satisfies + // the order requirement. + // NOTE THAT this field can as well be -1 already. + fallback_pos = m_iFirstNonOrderMsgPos; + } + else if (m_iDropPos != m_iEndPos) + { + // With TSBPD regard the drop position (regardless if + // TLPKTDROP is currently on or off), if "exists", that + // is, m_iDropPos != m_iEndPos. + fallback_pos = m_iDropPos; + } + + // This finds the first possible available packet, which is + // preferably at cell 0, but if not available, try also with + // given fallback position (unless it's -1). + const CPacket* pkt = tryAvailPacketAt(fallback_pos, (w_if.avail_range)); + if (pkt) + { + w_if.first_seq = pkt->getSeqNo(); + } +} + + +const CPacket* CRcvBuffer::tryAvailPacketAt(int pos, int& w_span) +{ + if (m_entries[m_iStartPos].status == EntryState_Avail) + { + pos = m_iStartPos; + w_span = offPos(m_iStartPos, m_iEndPos); + } + + if (pos == -1) + { + w_span = 0; + return NULL; + } + + SRT_ASSERT(m_entries[pos].pUnit != NULL); + + // TODO: we know that at least 1 packet is available, but only + // with m_iEndPos we know where the true range is. This could also + // be implemented for message mode, but still this would employ + // a separate begin-end range declared for a complete out-of-order + // message. + w_span = 1; + return &packetAt(pos); +} + +CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const int prev_max_off, const int newpktpos, const bool extended_end) +{ + time_point earlier_time; + + int prev_max_pos = incPos(m_iStartPos, prev_max_off); + + // Update flags + // Case [A] + if (extended_end) + { + // THIS means that the buffer WAS CONTIGUOUS BEFORE. + if (m_iEndPos == prev_max_pos) + { + // THIS means that the new packet didn't CAUSE a gap + if (m_iMaxPosOff == prev_max_off + 1) + { + // This means that m_iEndPos now shifts by 1, + // and m_iDropPos must be shifted together with it, + // as there's no drop to point. + m_iEndPos = incPos(m_iStartPos, m_iMaxPosOff); + m_iDropPos = m_iEndPos; + } + else + { + // Otherwise we have a drop-after-gap candidate + // which is the currently inserted packet. + // Therefore m_iEndPos STAYS WHERE IT IS. + m_iDropPos = incPos(m_iStartPos, m_iMaxPosOff - 1); + } + } + } + // + // Since this place, every newpktpos is in the range + // between m_iEndPos (inclusive) and a position for m_iMaxPosOff. + + // Here you can use prev_max_pos as the position represented + // by m_iMaxPosOff, as if !extended_end, it was unchanged. + else if (newpktpos == m_iEndPos) + { + // Case [D]: inserted a packet at the first gap following the + // contiguous region. This makes a potential to extend the + // contiguous region and we need to find its end. + + // If insertion happened at the very first packet, it is the + // new earliest packet now. In any other situation under this + // condition there's some contiguous packet range preceding + // this position. + if (m_iEndPos == m_iStartPos) + { + earlier_time = getPktTsbPdTime(unit->m_Packet.getMsgTimeStamp()); + } + + updateGapInfo(prev_max_pos); + } + // XXX Not sure if that's the best performant comparison + // What is meant here is that newpktpos is between + // m_iEndPos and m_iDropPos, though we know it's after m_iEndPos. + // CONSIDER: make m_iDropPos rather m_iDropOff, this will make + // this comparison a simple subtraction. Note that offset will + // have to be updated on every shift of m_iStartPos. + else if (cmpPos(newpktpos, m_iDropPos) < 0) + { + // Case [C]: the newly inserted packet precedes the + // previous earliest delivery position after drop, + // that is, there is now a "better" after-drop delivery + // candidate. + + // New position updated a valid packet on an earlier + // position than the drop position was before, although still + // following a gap. + // + // We know it because if the position has filled a gap following + // a valid packet, this preceding valid packet would be pointed + // by m_iDropPos, or it would point to some earlier packet in a + // contiguous series of valid packets following a gap, hence + // the above condition wouldn't be satisfied. + m_iDropPos = newpktpos; + + // If there's an inserted packet BEFORE drop-pos (which makes it + // a new drop-pos), while the very first packet is absent (the + // below condition), it means we have a new earliest-available + // packet. Otherwise we would have only a newly updated drop + // position, but still following some earlier contiguous range + // of valid packets - so it's earlier than previous drop, but + // not earlier than the earliest packet. + if (m_iStartPos == m_iEndPos) + { + earlier_time = getPktTsbPdTime(unit->m_Packet.getMsgTimeStamp()); + } + } + // OTHERWISE: case [D] in which nothing is to be updated. + + return earlier_time; } void CRcvBuffer::updateGapInfo(int prev_max_pos) diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 70099d355..f25528832 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -106,6 +106,8 @@ namespace srt // [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: @@ -263,6 +265,10 @@ class CRcvBuffer /// InsertInfo insert(CUnit* unit); + time_point updatePosInfo(const CUnit* unit, const int prev_max_off, const int newpktpos, const bool extended_end); + const CPacket* tryAvailPacketAt(int pos, int& w_span); + void getAvailInfo(InsertInfo& w_if); + /// Update the values of `m_iEndPos` and `m_iDropPos` in /// case when `m_iEndPos` was updated to a position of a /// nonempty cell. From 432b0c7de7a87547b9c8b1198e9eaeb2e8234e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 22 Feb 2024 17:49:47 +0100 Subject: [PATCH 48/62] Fixed some problems around macros that caused build breaks --- srtcore/api.h | 5 ++++- srtcore/core.h | 2 ++ srtcore/group.cpp | 13 ++++++++++++- srtcore/group.h | 20 +++++++++++++++++--- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/srtcore/api.h b/srtcore/api.h index 9a5c6081c..81b2d3633 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -125,7 +125,8 @@ class CUDTSocket // XXX Controversial as to whether it should be guarded by this lock. // It is used in many places without the lock, and it is also atomic. - SRT_ATTR_GUARDED_BY(m_ControlLock) + // (blocked because it makes more ado than actual help) + //SRT_ATTR_GUARDED_BY(m_ControlLock) sync::atomic m_Status; //< current socket state /// Time when the socket is closed. @@ -486,6 +487,8 @@ class CUDTUnited #endif void checkBrokenSockets(); + + SRT_ATTR_REQUIRES(m_GlobControlLock) void removeSocket(const SRTSOCKET u); CEPoll m_EPoll; // handling epoll data structures and events diff --git a/srtcore/core.h b/srtcore/core.h index e3ee7d2c4..b7a6a6be8 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -317,11 +317,13 @@ class CUDT bool isOPT_TsbPd() const { return m_config.bTSBPD; } int SRTT() const { return m_iSRTT; } int RTTVar() const { return m_iRTTVar; } + SRT_ATTR_REQUIRES(m_RecvAckLock) int32_t sndSeqNo() const { return m_iSndCurrSeqNo; } int32_t schedSeqNo() const { return m_iSndNextSeqNo; } bool overrideSndSeqNo(int32_t seq); #if ENABLE_BONDING + SRT_ATTR_REQUIRES(m_RecvAckLock) sync::steady_clock::time_point lastRspTime() const { return m_tsLastRspTime.load(); } sync::steady_clock::time_point freshActivationStart() const { return m_tsFreshActivation; } #endif diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 5d17fb6fb..a7e74922e 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -306,6 +306,10 @@ void CUDTGroup::updateErasedLink() // XXX THIS IS WEIRD. Locking at the and and looks like unfinished. // This also isn't currently called anywhere. +// +// Likely the plan was about having the fixed loss distance for balancing +// groups and this function should update this value on a change. +// See checkPacketArrivalLoss(). void CUDTGroup::updateInterlinkDistance() { // Before locking anything, check if you have good enough conditions @@ -627,6 +631,7 @@ void CUDTGroup::deriveSettings(CUDT* u) importOption(m_config, SRTO_PACKETFILTER, u->m_config.sPacketFilterConfig.str()); + // XXX reaching out to m_pCryptoControl here can be racy. importOption(m_config, SRTO_PBKEYLEN, u->m_pCryptoControl->KeyLen()); // Passphrase is empty by default. Decipher the passphrase and @@ -1007,6 +1012,7 @@ CRcvBuffer::InsertInfo CUDTGroup::addDataUnit(groups::SocketData* member, CUnit* } else if (info.result == CRcvBuffer::InsertInfo::DISCREPANCY) { + ScopedLock lk (m_RcvBufferLock); LOGC(qrlog.Error, log << CONID() << "grp/addDataUnit: " << "SEQUENCE DISCREPANCY. DISCARDING." << " seq=" << rpkt.seqno() @@ -1207,6 +1213,9 @@ bool CUDTGroup::checkBalancingLoss(const CPacket& pkt, CUDT::loss_seqs_t& w_loss { // We do have a recorded loss before. Get unit information. vector followers; + + // NOTE: calling m_Group.size() doesn't need locking of m_GroupLock. + // Getting elements or modifying a container does. m_pRcvBuffer->getUnitSeriesInfo(m_iRcvPossibleLossSeq, m_Group.size(), (followers)); // The "eclipse" condition is one of two: @@ -1223,7 +1232,9 @@ bool CUDTGroup::checkBalancingLoss(const CPacket& pkt, CUDT::loss_seqs_t& w_loss map nums; FringeValues(followers, (nums)); - IF_HEAVY_LOGGING(const char* which_condition[3] = {"fullcover", "longtail", "both???"}); +#if ENABLE_HEAVY_LOGGING + const char* which_condition[3] = {"fullcover", "longtail", "both???"}; +#endif bool longtail = false; bool fullcover = nums.size() >= m_Group.number_running(); diff --git a/srtcore/group.h b/srtcore/group.h index 252a1b8bf..aa8f25c7f 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -143,7 +143,9 @@ class CUDTGroup } // NEED LOCKING + SRT_ATTR_REQUIRES(m_GroupLock) gli_t begin() { return m_Group.begin(); } + SRT_ATTR_REQUIRES(m_GroupLock) gli_t end() { return m_Group.end(); } /// Remove the socket from the group container. @@ -464,10 +466,13 @@ class CUDTGroup m_List.clear(); m_SizeCache = 0; } - size_t size() { return m_SizeCache; } void erase(gli_t it); + // NOTE: These methods below don't need locking in RO version. + SRTU_PROPERTY_RO(size_t, size, m_SizeCache); + + // UPDATED BY: updateRcvRunningState() (which needs locking). SRTU_PROPERTY_RW(size_t, number_running, m_zNumberRunning); }; @@ -483,7 +488,7 @@ class CUDTGroup void updateInterlinkDistance(); private: - SRT_ATTR_PT_GUARDED_BY(m_GroupLock) + SRT_ATTR_GUARDED_BY(m_GroupLock) GroupContainer m_Group; SRT_GROUP_TYPE m_type; srt::sync::atomic m_iBusy; @@ -735,7 +740,7 @@ class CUDTGroup sync::Mutex m_SndLastSeqLock; - SRT_ATTR_GUARDED_BY(m_iSndLastSeqLock) + SRT_ATTR_GUARDED_BY(m_SndLastSeqLock) sync::atomic m_SndLastSeqNo; public: @@ -750,6 +755,7 @@ class CUDTGroup return m_SndLastSeqNo; } + SRT_ATTR_REQUIRES(m_SndLastSeqLock) int32_t getSentSeq() const { return m_SndLastSeqNo; @@ -903,7 +909,11 @@ class CUDTGroup int checkLazySpawnTsbPdThread(); CRcvBuffer::InsertInfo addDataUnit(SocketData* member, CUnit* u, CUDT::loss_seqs_t&, bool&); bool checkPacketArrivalLoss(SocketData* member, const CPacket& rpkt, CUDT::loss_seqs_t&); + + SRT_ATTR_REQUIRES(m_RcvBufferLock) bool checkBalancingLoss(const CPacket& rpkt, CUDT::loss_seqs_t&); + + SRT_ATTR_REQUIRES(m_RcvBufferLock) int rcvDropTooLateUpTo(int32_t seqno); void synchronizeLoss(int32_t seqno); void addGroupDriftSample(uint32_t timestamp, const time_point& tsArrival, int rtt); @@ -937,6 +947,7 @@ class CUDTGroup return false; } + SRT_ATTR_REQUIRES(m_GroupLock) bool applyGroupSequences(SRTSOCKET, int32_t& w_snd_isn, int32_t& w_rcv_isn); void updateLatestRcv(srt::CUDTSocket*); @@ -949,6 +960,7 @@ class CUDTGroup void updateOnACK(int32_t ackdata_seqno); int packLostData(CUDT* core, CPacket& w_packet, int32_t exp_seq); + SRT_ATTR_REQUIRES(m_RcvBufferLock) time_point getPktTsbPdTime(uint32_t usPktTimestamp) const { return m_pRcvBuffer->getPktTsbPdTime(usPktTimestamp); @@ -969,6 +981,8 @@ class CUDTGroup SRTU_PROPERTY_RRW(std::set&, epollset, m_sPollID); SRTU_PROPERTY_RW_CHAIN(CUDTGroup, int64_t, latency_us, m_iTsbPdDelay_us); SRTU_PROPERTY_RO(bool, closing, m_bClosing); + + SRT_ATTR_REQUIRES(m_RcvBufferLock) // RO provides only one getter method SRTU_PROPERTY_RO(int32_t, getOldestRcvSeqNo, m_pRcvBuffer->getStartSeqNo()); }; From f416392ade5202d861ebab649715e781dd880c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Thu, 22 Feb 2024 18:04:36 +0100 Subject: [PATCH 49/62] Fixed gcc extension causing BB on Windows --- srtcore/group.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/srtcore/group.cpp b/srtcore/group.cpp index a7e74922e..973a87bc8 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -5466,8 +5466,8 @@ CUDTGroup::gli_t CUDTGroup::linkSelect_window(const CUDTGroup::BalancingLinkStat flight += 2; // prevent having 0 used for equations total_flight += flight; - - linkdata.push_back( (LinkCapableData){li, flight} ); + LinkCapableData lcd = {li, flight}; + linkdata.push_back(lcd); } if (linkdata.empty()) From 5b9975454a0d771b5e669ae00e8bfb9e08b4db88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 26 Feb 2024 09:11:41 +0100 Subject: [PATCH 50/62] Fixed some build breaks reported by CI --- srtcore/core.cpp | 2 +- srtcore/group.cpp | 9 +++++---- srtcore/queue.cpp | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 70e0e1d62..38d6ba1c5 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9762,7 +9762,7 @@ void srt::CUDT::updateAfterSrtHandshake(int hsv) } } -int srt::CUDT::packLostData(CPacket& w_packet, int32_t exp_seq) +int srt::CUDT::packLostData(CPacket& w_packet, int32_t exp_seq SRT_ATR_UNUSED) { // protect m_iSndLastDataAck from updating by ACK processing UniqueLock ackguard(m_RecvAckLock); diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 973a87bc8..472fc0ad5 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -991,7 +991,7 @@ CRcvBuffer::InsertInfo CUDTGroup::addDataUnit(groups::SocketData* member, CUnit* if (info.result == CRcvBuffer::InsertInfo::INSERTED) { - w_have_loss = checkPacketArrivalLoss(member, u->m_Packet, (w_losses)); + w_have_loss = checkPacketArrivalLoss(member, rpkt, (w_losses)); } } @@ -5726,7 +5726,7 @@ int CUDTGroup::packLostData(CUDT* core, CPacket& w_packet, int32_t exp_seq) // XXX This is temporarily used for broadcast with common loss list. bool have_extracted = false; - const char* as = "FIRST FOUND"; + IF_HEAVY_LOGGING(const char* as = "FIRST FOUND"); if (exp_seq == SRT_SEQNO_NONE) { exp_seq = m_pSndLossList->popLostSeq(); @@ -5734,7 +5734,7 @@ int CUDTGroup::packLostData(CUDT* core, CPacket& w_packet, int32_t exp_seq) } else { - as = "EXPECTED"; + IF_HEAVY_LOGGING(as = "EXPECTED"); have_extracted = m_pSndLossList->popLostSeq(exp_seq); } @@ -5966,8 +5966,9 @@ void* CUDTGroup::tsbpd(void* param) // follow the drop, but this must be done outside the lock on the buffer. synch_loss_after_drop = iDropCnt; +#if ENABLE_LOGGING const int64_t timediff_us = count_microseconds(tnow - info.tsbpd_time); - +#endif #if ENABLE_HEAVY_LOGGING HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: DROPSEQ: up to seqno %" << CSeqNo::decseq(info.seqno) << " (" diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 1d396f7bc..e5e09184f 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -65,7 +65,7 @@ using namespace std; using namespace srt::sync; using namespace srt_logging; -srt::CUnitQueue::CUnitQueue(int initNumUnits, int mss, UDPSOCKET owner) +srt::CUnitQueue::CUnitQueue(int initNumUnits, int mss, SRTSOCKET owner) : m_iNumTaken(0) , m_iMSS(mss) , m_iBlockSize(initNumUnits) From 328c35989fff1186d2fd28308bb04cd38335afef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 26 Feb 2024 12:05:06 +0100 Subject: [PATCH 51/62] Fixed C++03 incompat case --- srtcore/group.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 472fc0ad5..a3f34c484 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -3276,7 +3276,8 @@ class StabilityTracer std::string str_tnow = srt::sync::FormatTimeSys(srt::sync::steady_clock::now()); str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [SYST]' part - while (str_tnow.find(':') != std::string::npos) { + while (str_tnow.find(':') != std::string::npos) + { str_tnow.replace(str_tnow.find(':'), 1, 1, '_'); } const std::string fname = "stability_trace_" + str_tnow + ".csv"; @@ -5606,11 +5607,12 @@ CUDTGroup::gli_t CUDTGroup::linkSelect_window(const CUDTGroup::BalancingLinkStat // [[using locked(m_GroupLock)]] bool CUDTGroup::updateSendPacketUnique_LOCKED(int32_t single_seq) { + SchedSeq this_seq_as_fresh = {single_seq, groups::SQT_FRESH}; // Check first if the packet wasn't already scheduled // If so, do nothing and return success. for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d) { - if (find(d->send_schedule.begin(), d->send_schedule.end(), (SchedSeq){single_seq, groups::SQT_FRESH}) != d->send_schedule.end()) + if (find(d->send_schedule.begin(), d->send_schedule.end(), this_seq_as_fresh) != d->send_schedule.end()) { HLOGC(gmlog.Debug, log << "grp/schedule(fresh): already scheduled to %" << d->id << " - skipping"); return true; // because this should be considered successful, even though didn't schedule. @@ -5629,7 +5631,7 @@ bool CUDTGroup::updateSendPacketUnique_LOCKED(int32_t single_seq) HLOGC(gmlog.Debug, log << "grp/schedule(fresh): scheduling %" << single_seq << " to @" << selink->id); - selink->send_schedule.push_back((groups::SchedSeq){single_seq, groups::SQT_FRESH}); + selink->send_schedule.push_back(this_seq_as_fresh); m_Group.set_active(selink); // XXX @@ -5692,7 +5694,8 @@ bool CUDTGroup::updateSendPacketLoss(bool use_send_sched, const std::vector< std } HLOGC(gmlog.Debug, log << "grp/schedule(loss): schedule REXMIT %" << seq << " to @" << selink->id); - selink->send_schedule.push_back((SchedSeq){seq, groups::SQT_LOSS}); + SchedSeq scheduled_loss = {seq, groups::SQT_LOSS}; + selink->send_schedule.push_back(scheduled_loss); lstate.ilink = selink; } } From 283021efbaca001e3b0f0de8fdea51b8de173ff9 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Wed, 28 Feb 2024 11:03:46 +0100 Subject: [PATCH 52/62] Apply suggestions from code review (further impossible fixes pending) Co-authored-by: Maxim Sharabayko --- srtcore/buffer_rcv.cpp | 1 - srtcore/buffer_rcv.h | 8 ++++---- srtcore/core.cpp | 4 ++-- srtcore/core.h | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 97b76d755..6d6ebd841 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -656,7 +656,6 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pairstatus_: 0: free, 1: good, 2: read, 3: dropped (can be combined with read?) // // thread safety: -// start_pos_: CUDT::m_RecvLock +// m_iStartPos: CUDT::m_RecvLock // first_unack_pos_: CUDT::m_AckLock -// max_pos_inc_: none? (modified on add and ack -// first_nonread_pos_: +// m_iMaxPosOff: none? (modified on add and ack +// m_iFirstNonreadPos: // // // m_iStartPos: the first packet that should be read (might be empty) @@ -455,7 +455,7 @@ class CRcvBuffer return m_iMaxPosOff; } - // Unfortunately it still needs locking. + // Returns true if the buffer is full. Requires locking. bool full() const { return size() == capacity(); diff --git a/srtcore/core.cpp b/srtcore/core.cpp index eaea48102..9afa74c9b 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -5544,7 +5544,7 @@ void * srt::CUDT::tsbpd(void* param) tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } - SRT_ATR_UNUSED bool wakeup_on_signal = true; + SRT_ATR_UNUSED bool bWakeupOnSignal = true; // We may just briefly unlocked the m_RecvLock, so we need to check m_bClosing again to avoid deadlock. if (self->m_bClosing) @@ -8183,7 +8183,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) } } #endif - // Signalling m_RecvDataCond is not dane when TSBPD is on. + // Signalling m_RecvDataCond is not done when TSBPD is on. // This signalling is done in file mode in order to keep the // API reader thread sleeping until there is a "bigger portion" // of data to read. In TSBPD mode this isn't done because every diff --git a/srtcore/core.h b/srtcore/core.h index 8cff2fbce..dee7bd48d 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -987,7 +987,7 @@ class CUDT sync::CThread m_RcvTsbPdThread; // Rcv TsbPD Thread handle sync::Condition m_RcvTsbPdCond; // TSBPD signals if reading is ready. Use together with m_RecvLock - sync::atomic m_bWakeOnRecv; // Expected to be woken up when received a packet + sync::atomic m_bWakeOnRecv; // Expected to wake up TSBPD when a read-ready data packet is received. sync::Mutex m_RcvTsbPdStartupLock; // Protects TSBPD thread creating and joining CallbackHolder m_cbAcceptHook; From b969a8329e9275d6df2f8979a4b803de7499eaaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Wed, 28 Feb 2024 12:06:29 +0100 Subject: [PATCH 53/62] Remaining post-review fixes --- srtcore/buffer_rcv.h | 5 +-- srtcore/core.cpp | 83 ++++++++++++++++---------------------------- 2 files changed, 33 insertions(+), 55 deletions(-) diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 93b51c2c8..24f5066f6 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -338,9 +338,10 @@ class CRcvBuffer /// Read the whole message from one or several packets. /// - /// @param [in,out] data buffer to write the message into. + /// @param [out] data buffer to write the message into. /// @param [in] len size of the buffer. - /// @param [in,out] message control data + /// @param [out,opt] message control data to be filled + /// @param [out,opt] pw_seqrange range of sequence numbers for packets belonging to the message /// /// @return actual number of bytes extracted from the buffer. /// 0 if nothing to read. diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 9afa74c9b..45aeb2b4c 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -5562,7 +5562,7 @@ void * srt::CUDT::tsbpd(void* param) log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << info.seqno << " T=" << FormatTime(tsNextDelivery) << " - waiting " << FormatDuration(timediff)); THREAD_PAUSED(); - wakeup_on_signal = tsbpd_cc.wait_until(tsNextDelivery); + bWakeupOnSignal = tsbpd_cc.wait_until(tsNextDelivery); THREAD_RESUMED(); } else @@ -5585,7 +5585,7 @@ void * srt::CUDT::tsbpd(void* param) THREAD_RESUMED(); } - HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP [" << (wakeup_on_signal ? "signal" : "timeout") << "]!!! - " + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP [" << (bWakeupOnSignal? "signal" : "timeout") << "]!!! - " << "NOW=" << FormatTime(steady_clock::now())); } THREAD_EXIT(); @@ -8048,14 +8048,13 @@ bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) LOGP(cnlog.Error, "IPE: ack can't be sent, buffer doesn't exist and no group membership"); return false; } - { - ScopedLock buflock (m_RcvBufferLock); - bool has_followers = m_pRcvBuffer->getContiguousEnd((w_seq)); - if (has_followers) - w_log_reason = "first lost"; - else - w_log_reason = "expected next"; - } + + ScopedLock buflock (m_RcvBufferLock); + bool has_followers = m_pRcvBuffer->getContiguousEnd((w_seq)); + if (has_followers) + w_log_reason = "first lost"; + else + w_log_reason = "expected next"; return true; } @@ -9090,34 +9089,27 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * avgpayloadsz, (uint32_t) iDropCnt)); } } - // When the drop request was received, it means that there are - // packets for which there will never be ACK sent; if the TSBPD thread - // is currently in the ACK-waiting state, it may never exit. - if (m_bTsbPd) - { - // XXX Likely this is not necessary because: - // 1. In the recv-waiting state, that is, when TSBPD thread - // sleeps forever, it will be woken up anyway on packet - // reception. - // 2. If there are any packets in the buffer and the initial - // packet cell is empty (in which situation any drop could - // occur), TSBPD thread is sleeping timely, until the playtime - // of the first "drop up to" packet. Dropping changes nothing here. - // 3. If the buffer is empty, there's nothing "to drop up to", so - // this function will not change anything in the buffer and so - // in the reception state as well. - // 4. If the TSBPD thread is waiting until a play-ready packet is - // retrieved by the API call (in which case it is also sleeping - // forever until it's woken up by the API call), it may remain - // stalled forever, if the application isn't reading the play-ready - // packet. But this means that the application got stalled anyway. - // TSBPD when woken up could at best state that there's still a - // play-ready packet that is still not retrieved and fall back - // to sleep (forever). - - //HLOGP(inlog.Debug, "DROPREQ: signal TSBPD"); - //rcvtscc.notify_one(); - } + + // NOTE: + // PREVIOUSLY done: notify on rcvtscc if m_bTsbPd + // OLD COMMENT: + // // When the drop request was received, it means that there are + // // packets for which there will never be ACK sent; if the TSBPD thread + // // is currently in the ACK-waiting state, it may never exit. + // + // Likely this is no longer necessary because: + // + // 1. If there's a play-ready packet, either in cell 0 or + // after a drop, TSBPD is sleeping timely, up to the play-time + // of the next ready packet (and the drop region concerned here + // is still a gap to be skipped for this). + // 2. TSBPD sleeps forever when the buffer is empty, in which case + // it will be always woken up on packet reception (see m_bWakeOnRecv). + // And dropping won't happen in that case anyway. Note that the drop + // request will not drop packets that are already received. + // 3. TSBPD sleeps forever when the API call didn't extract the + // data that are ready to play. This isn't a problem if nothing + // except the API call would wake it up. } dropFromLossLists(dropdata[0], dropdata[1]); @@ -10679,21 +10671,6 @@ int srt::CUDT::processData(CUnit* in_unit) HLOGC(qrlog.Debug, log << CONID() << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs)); sendLossReport(srt_loss_seqs); } - - // This should not be required with the new receiver buffer because - // signalling happens on every packet reception, if it has changed the - // earliest packet position. - /* - if (m_bTsbPd) - { - HLOGC(qrlog.Debug, log << CONID() << "loss: signaling TSBPD cond"); - CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); - } - else - { - HLOGC(qrlog.Debug, log << CONID() << "loss: socket is not TSBPD, not signaling"); - } - */ } // Separately report loss records of those reported by a filter. From d9f079a3648acd219608784135201bf5e4d4494b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 3 Jun 2024 10:32:53 +0200 Subject: [PATCH 54/62] Turned integer-based values to strong types. CHECKPOINT: tests passed --- srtcore/buffer_rcv.cpp | 394 ++++++++++++++++++++++++----------------- srtcore/buffer_rcv.h | 215 ++++++++++++++++++---- srtcore/common.h | 2 + srtcore/core.cpp | 32 +--- srtcore/utilities.h | 20 ++- 5 files changed, 435 insertions(+), 228 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 6d6ebd841..ab72670df 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -78,18 +78,19 @@ namespace { // 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. - bool isInRange(int iStartPos, int iMaxPosOff, size_t iSize, int iFirstNonreadPos) + bool isInRange(CPos iStartPos, COff iMaxPosOff, size_t iSize, CPos iFirstNonreadPos) { if (iFirstNonreadPos == iStartPos) return true; - const int iLastPos = (iStartPos + iMaxPosOff) % iSize; - const bool isOverrun = iLastPos < iStartPos; + //const int iLastPos = (iStartPos VALUE + iMaxPosOff VALUE) % iSize; + const CPos iLastPos = iStartPos + iMaxPosOff; + const bool isOverrun = iLastPos VALUE < iStartPos VALUE; if (isOverrun) - return iFirstNonreadPos > iStartPos || iFirstNonreadPos <= iLastPos; + return iFirstNonreadPos VALUE > iStartPos VALUE || iFirstNonreadPos VALUE <= iLastPos VALUE; - return iFirstNonreadPos > iStartPos && iFirstNonreadPos <= iLastPos; + return iFirstNonreadPos VALUE > iStartPos VALUE && iFirstNonreadPos VALUE <= iLastPos VALUE; } } @@ -120,14 +121,14 @@ CRcvBuffer::CRcvBuffer(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool b , m_szSize(size) // TODO: maybe just use m_entries.size() , m_pUnitQueue(unitqueue) , m_iStartSeqNo(initSeqNo) // NOTE: SRT_SEQNO_NONE is allowed here. - , m_iStartPos(0) - , m_iEndPos(0) - , m_iDropPos(0) - , m_iFirstNonreadPos(0) + , m_iStartPos(&m_szSize, 0) + , m_iEndPos(&m_szSize, 0) + , m_iDropPos(&m_szSize, 0) + , m_iFirstNonreadPos(&m_szSize, 0) , m_iMaxPosOff(0) , m_iNotch(0) , m_numNonOrderPackets(0) - , m_iFirstNonOrderMsgPos(-1) + , m_iFirstNonOrderMsgPos(&m_szSize, CPos_TRAP.val()) , m_bPeerRexmitFlag(true) , m_bMessageAPI(bMessageAPI) , m_iBytesCount(0) @@ -152,29 +153,34 @@ CRcvBuffer::~CRcvBuffer() void CRcvBuffer::debugShowState(const char* source SRT_ATR_UNUSED) { - HLOGC(brlog.Debug, log << "RCV-BUF-STATE(" << source << ") start=" << m_iStartPos << " end=" << m_iEndPos - << " drop=" << m_iDropPos << " max-off=+" << m_iMaxPosOff << " seq[start]=%" << m_iStartSeqNo); + HLOGC(brlog.Debug, log << "RCV-BUF-STATE(" << source + << ") start=" << m_iStartPos VALUE + << " end=" << m_iEndPos VALUE + << " drop=" << m_iDropPos VALUE + << " max-off=+" << m_iMaxPosOff VALUE + << " seq[start]=%" << m_iStartSeqNo VALUE); } CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) { SRT_ASSERT(unit != NULL); const int32_t seqno = unit->m_Packet.getSeqNo(); - const int offset = CSeqNo::seqoff(m_iStartSeqNo, seqno); + //const int offset = CSeqNo::seqoff(m_iStartSeqNo, seqno); + const COff offset = COff(CSeqNo(seqno) - m_iStartSeqNo); IF_RCVBUF_DEBUG(ScopedLog scoped_log); IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::insert: seqno " << seqno); IF_RCVBUF_DEBUG(scoped_log.ss << " msgno " << unit->m_Packet.getMsgSeq(m_bPeerRexmitFlag)); IF_RCVBUF_DEBUG(scoped_log.ss << " m_iStartSeqNo " << m_iStartSeqNo << " offset " << offset); - if (offset < 0) + if (offset < COff(0)) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -2"); return InsertInfo(InsertInfo::BELATED); } IF_HEAVY_LOGGING(string debug_source = "insert %" + Sprint(seqno)); - if (offset >= (int)capacity()) + if (offset >= COff(capacity())) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -3"); @@ -188,14 +194,14 @@ 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) VALUE / m_szSize < 2); - const int newpktpos = incPos(m_iStartPos, offset); - const int prev_max_off = m_iMaxPosOff; + const CPos newpktpos = m_iStartPos + offset; + const COff prev_max_off = m_iMaxPosOff; bool extended_end = false; if (offset >= m_iMaxPosOff) { - m_iMaxPosOff = offset + 1; + m_iMaxPosOff = offset + COff(1); extended_end = true; } @@ -204,7 +210,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 VALUE >= 0 && newpktpos VALUE < int(m_szSize)); if (m_entries[newpktpos].status != EntryState_Empty) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -1"); @@ -247,7 +253,7 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) void CRcvBuffer::getAvailInfo(CRcvBuffer::InsertInfo& w_if) { - int fallback_pos = -1; + CPos fallback_pos = CPos_TRAP; if (!m_tsbpd.isEnabled()) { // In case when TSBPD is off, we take into account the message mode @@ -271,22 +277,23 @@ void CRcvBuffer::getAvailInfo(CRcvBuffer::InsertInfo& w_if) const CPacket* pkt = tryAvailPacketAt(fallback_pos, (w_if.avail_range)); if (pkt) { - w_if.first_seq = pkt->getSeqNo(); + w_if.first_seq = CSeqNo(pkt->getSeqNo()); } } -const CPacket* CRcvBuffer::tryAvailPacketAt(int pos, int& w_span) +const CPacket* CRcvBuffer::tryAvailPacketAt(CPos pos, COff& w_span) { if (m_entries[m_iStartPos].status == EntryState_Avail) { pos = m_iStartPos; - w_span = offPos(m_iStartPos, m_iEndPos); + //w_span = offPos(m_iStartPos, m_iEndPos); + w_span = m_iEndPos - m_iStartPos; } - if (pos == -1) + if (pos == CPos_TRAP) { - w_span = 0; + w_span = COff(0); return NULL; } @@ -297,15 +304,16 @@ const CPacket* CRcvBuffer::tryAvailPacketAt(int pos, int& w_span) // be implemented for message mode, but still this would employ // a separate begin-end range declared for a complete out-of-order // message. - w_span = 1; + w_span = COff(1); return &packetAt(pos); } -CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const int prev_max_off, const int newpktpos, const bool extended_end) +CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff prev_max_off, const CPos newpktpos, const bool extended_end) { time_point earlier_time; - int prev_max_pos = incPos(m_iStartPos, prev_max_off); + //int prev_max_pos = incPos(m_iStartPos, prev_max_off); + CPos prev_max_pos = m_iStartPos + prev_max_off; // Update flags // Case [A] @@ -320,7 +328,8 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const int pr // This means that m_iEndPos now shifts by 1, // and m_iDropPos must be shifted together with it, // as there's no drop to point. - m_iEndPos = incPos(m_iStartPos, m_iMaxPosOff); + //m_iEndPos = incPos(m_iStartPos, m_iMaxPosOff); + m_iEndPos = m_iStartPos + m_iMaxPosOff; m_iDropPos = m_iEndPos; } else @@ -328,7 +337,8 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const int pr // Otherwise we have a drop-after-gap candidate // which is the currently inserted packet. // Therefore m_iEndPos STAYS WHERE IT IS. - m_iDropPos = incPos(m_iStartPos, m_iMaxPosOff - 1); + //m_iDropPos = incPos(m_iStartPos, m_iMaxPosOff - 1); + m_iDropPos = m_iStartPos + (m_iMaxPosOff - 1); } } } @@ -361,7 +371,9 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const int pr // CONSIDER: make m_iDropPos rather m_iDropOff, this will make // this comparison a simple subtraction. Note that offset will // have to be updated on every shift of m_iStartPos. - else if (cmpPos(newpktpos, m_iDropPos) < 0) + + //else if (cmpPos(newpktpos, m_iDropPos) < 0) + else if (newpktpos.cmp(m_iDropPos, m_iStartPos) < 0) { // Case [C]: the newly inserted packet precedes the // previous earliest delivery position after drop, @@ -396,12 +408,12 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const int pr return earlier_time; } -void CRcvBuffer::updateGapInfo(int prev_max_pos) +void CRcvBuffer::updateGapInfo(CPos prev_max_pos) { - int pos = m_iEndPos; + CPos pos = m_iEndPos; // First, search for the next gap, max until m_iMaxPosOff. - for ( ; pos != prev_max_pos; pos = incPos(pos)) + for ( ; pos != prev_max_pos; ++pos /*pos = incPos(pos)*/) { if (m_entries[pos].status == EntryState_Empty) { @@ -420,7 +432,7 @@ void CRcvBuffer::updateGapInfo(int prev_max_pos) m_iEndPos = pos; m_iDropPos = pos; // fallback, although SHOULD be impossible // So, search for the first position to drop up to. - for ( ; pos != prev_max_pos; pos = incPos(pos)) + for ( ; pos != prev_max_pos; ++pos /*pos = incPos(pos)*/) { if (m_entries[pos].status != EntryState_Empty) { @@ -440,7 +452,8 @@ int CRcvBuffer::dropUpTo(int32_t seqno) IF_RCVBUF_DEBUG(ScopedLog scoped_log); IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::dropUpTo: seqno " << seqno << " m_iStartSeqNo " << m_iStartSeqNo); - int len = CSeqNo::seqoff(m_iStartSeqNo, seqno); + COff len = COff(CSeqNo(seqno) - m_iStartSeqNo); + //int len = CSeqNo::seqoff(m_iStartSeqNo, seqno); if (len <= 0) { IF_RCVBUF_DEBUG(scoped_log.ss << ". Nothing to drop."); @@ -451,25 +464,27 @@ int CRcvBuffer::dropUpTo(int32_t seqno) if (m_iMaxPosOff < 0) m_iMaxPosOff = 0; - const int iDropCnt = len; - while (len > 0) + const int iDropCnt = len VALUE; + while (len VALUE > 0) { dropUnitInPos(m_iStartPos); m_entries[m_iStartPos].status = EntryState_Empty; SRT_ASSERT(m_entries[m_iStartPos].pUnit == NULL && m_entries[m_iStartPos].status == EntryState_Empty); - m_iStartPos = incPos(m_iStartPos); + //m_iStartPos = incPos(m_iStartPos); + ++m_iStartPos; --len; } // Update positions - m_iStartSeqNo = seqno; + m_iStartSeqNo = CSeqNo(seqno); // Move forward if there are "read/drop" entries. // (This call MAY shift m_iStartSeqNo further.) releaseNextFillerEntries(); // Start from here and search fort the next gap m_iEndPos = m_iDropPos = m_iStartPos; - updateGapInfo(incPos(m_iStartPos, m_iMaxPosOff)); + //updateGapInfo(incPos(m_iStartPos, m_iMaxPosOff)); + updateGapInfo(m_iStartPos + m_iMaxPosOff); // If the nonread position is now behind the starting position, set it to the starting position and update. // Preceding packets were likely missing, and the non read position can probably be moved further now. @@ -490,7 +505,8 @@ int CRcvBuffer::dropAll() if (empty()) return 0; - const int end_seqno = CSeqNo::incseq(m_iStartSeqNo, m_iMaxPosOff); + //const int end_seqno = CSeqNo::incseq(m_iStartSeqNo, m_iMaxPosOff); + const int end_seqno = (m_iStartSeqNo + m_iMaxPosOff VALUE) VALUE; return dropUpTo(end_seqno); } @@ -502,24 +518,28 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro << m_iStartSeqNo); // Drop by packet seqno range to also wipe those packets that do not exist in the buffer. - const int offset_a = CSeqNo::seqoff(m_iStartSeqNo, seqnolo); - const int offset_b = CSeqNo::seqoff(m_iStartSeqNo, seqnohi); + //const int offset_a = CSeqNo::seqoff(m_iStartSeqNo, seqnolo); + //const int offset_b = CSeqNo::seqoff(m_iStartSeqNo, seqnohi); + const int offset_a = CSeqNo(seqnolo) - m_iStartSeqNo; + const int offset_b = CSeqNo(seqnohi) - m_iStartSeqNo; if (offset_b < 0) { LOGC(rbuflog.Debug, log << "CRcvBuffer.dropMessage(): nothing to drop. Requested [" << seqnolo << "; " - << seqnohi << "]. Buffer start " << m_iStartSeqNo << "."); + << seqnohi << "]. Buffer start " << m_iStartSeqNo VALUE << "."); return 0; } const bool bKeepExisting = (actionOnExisting == KEEP_EXISTING); - int minDroppedOffset = -1; + COff minDroppedOffset = COff(-1); int iDropCnt = 0; - const int start_off = max(0, offset_a); - const int start_pos = incPos(m_iStartPos, start_off); - const int end_off = min((int) m_szSize - 1, offset_b + 1); - const int end_pos = incPos(m_iStartPos, end_off); + const COff start_off = COff(max(0, offset_a)); + //const int start_pos = incPos(m_iStartPos, start_off); + const CPos start_pos = m_iStartPos + start_off; + const COff end_off = COff(min((int) m_szSize - 1, offset_b + 1)); + //const int end_pos = incPos(m_iStartPos, end_off); + const CPos end_pos = m_iStartPos + end_off; bool bDropByMsgNo = msgno > SRT_MSGNO_CONTROL; // Excluding both SRT_MSGNO_NONE (-1) and SRT_MSGNO_CONTROL (0). - for (int i = start_pos; i != end_pos; i = incPos(i)) + for (CPos i = start_pos; i != end_pos; ++i) { // Check if the unit was already dropped earlier. if (m_entries[i].status == EntryState_Drop) @@ -557,7 +577,8 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro ++iDropCnt; m_entries[i].status = EntryState_Drop; if (minDroppedOffset == -1) - minDroppedOffset = offPos(m_iStartPos, i); + //minDroppedOffset = offPos(m_iStartPos, i); + minDroppedOffset = i - m_iStartPos; } if (bDropByMsgNo) @@ -567,8 +588,9 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro // The sender should have the last packet of the message it is requesting to be dropped. // Therefore we don't search forward, but need to check earlier packets in the RCV buffer. // Try to drop by the message number in case the message starts earlier than @a seqnolo. - const int stop_pos = decPos(m_iStartPos); - for (int i = start_pos; i != stop_pos; i = decPos(i)) + //const int stop_pos = decPos(m_iStartPos); + const CPos stop_pos = m_iStartPos - COff(1); + for (CPos i = start_pos; i != stop_pos; --i) { // Can't drop if message number is not known. if (!m_entries[i].pUnit) // also dropped earlier. @@ -591,7 +613,8 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro dropUnitInPos(i); m_entries[i].status = EntryState_Drop; // As the search goes backward, i is always earlier than minDroppedOffset. - minDroppedOffset = offPos(m_iStartPos, i); + //minDroppedOffset = offPos(m_iStartPos, i); + minDroppedOffset = i - m_iStartPos; // Break the loop if the start of the message has been found. No need to search further. if (bnd == PB_FIRST) @@ -619,7 +642,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro if (!m_tsbpd.isEnabled() && m_bMessageAPI) { if (!checkFirstReadableNonOrder()) - m_iFirstNonOrderMsgPos = -1; + m_iFirstNonOrderMsgPos = CPos_TRAP; updateFirstReadableNonOrder(); } @@ -632,16 +655,20 @@ bool CRcvBuffer::getContiguousEnd(int32_t& w_seq) const if (m_iStartPos == m_iEndPos) { // Initial contiguous region empty (including empty buffer). - HLOGC(rbuflog.Debug, log << "CONTIG: empty, give up base=%" << m_iStartSeqNo); - w_seq = m_iStartSeqNo; + HLOGC(rbuflog.Debug, log << "CONTIG: empty, give up base=%" << m_iStartSeqNo VALUE); + w_seq = m_iStartSeqNo VALUE; return m_iMaxPosOff > 0; } - int end_off = offPos(m_iStartPos, m_iEndPos); + //int end_off = offPos(m_iStartPos, m_iEndPos); + COff end_off = m_iEndPos - m_iStartPos; - w_seq = CSeqNo::incseq(m_iStartSeqNo, end_off); + //w_seq = CSeqNo::incseq(m_iStartSeqNo, end_off); + w_seq = (m_iStartSeqNo + end_off VALUE) VALUE; - HLOGC(rbuflog.Debug, log << "CONTIG: endD=" << end_off << " maxD=" << m_iMaxPosOff << " base=%" << m_iStartSeqNo + HLOGC(rbuflog.Debug, log << "CONTIG: endD=" << end_off VALUE + << " maxD=" << m_iMaxPosOff VALUE + << " base=%" << m_iStartSeqNo VALUE << " end=%" << w_seq); return (end_off < m_iMaxPosOff); @@ -650,13 +677,13 @@ bool CRcvBuffer::getContiguousEnd(int32_t& w_seq) const int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair* pw_seqrange) { const bool canReadInOrder = hasReadableInorderPkts(); - if (!canReadInOrder && m_iFirstNonOrderMsgPos < 0) + if (!canReadInOrder && m_iFirstNonOrderMsgPos == CPos_TRAP) { LOGC(rbuflog.Warn, log << "CRcvBuffer.readMessage(): nothing to read. Ignored isRcvDataReady() result?"); return 0; } - const int readPos = canReadInOrder ? m_iStartPos : m_iFirstNonOrderMsgPos; + const CPos readPos = canReadInOrder ? m_iStartPos : m_iFirstNonOrderMsgPos; const bool isReadingFromStart = (readPos == m_iStartPos); // Indicates if the m_iStartPos can be changed IF_RCVBUF_DEBUG(ScopedLog scoped_log); @@ -670,7 +697,7 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair= 0); - m_iStartSeqNo = CSeqNo::incseq(pktseqno); + m_iStartSeqNo = CSeqNo(pktseqno) + 1; } else { @@ -747,7 +774,7 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair= remain_pktlen) { releaseUnitInPos(p); - p = incPos(p); + //p = incPos(p); + ++p; m_iNotch = 0; m_iStartPos = p; --m_iMaxPosOff; - SRT_ASSERT(m_iMaxPosOff >= 0); - m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); + SRT_ASSERT(m_iMaxPosOff VALUE >= 0); + //m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); + ++m_iStartSeqNo; } else m_iNotch += rs; @@ -923,7 +961,8 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) if (iBytesRead == 0) { - LOGC(rbuflog.Error, log << "readBufferTo: 0 bytes read. m_iStartPos=" << m_iStartPos << ", m_iFirstNonreadPos=" << m_iFirstNonreadPos); + LOGC(rbuflog.Error, log << "readBufferTo: 0 bytes read. m_iStartPos=" << m_iStartPos VALUE + << ", m_iFirstNonreadPos=" << m_iFirstNonreadPos VALUE); } IF_HEAVY_LOGGING(debugShowState("readbuf")); @@ -942,12 +981,13 @@ int CRcvBuffer::readBufferToFile(fstream& ofs, int len) bool CRcvBuffer::hasAvailablePackets() const { - return hasReadableInorderPkts() || (m_numNonOrderPackets > 0 && m_iFirstNonOrderMsgPos != -1); + return hasReadableInorderPkts() || (m_numNonOrderPackets > 0 && m_iFirstNonOrderMsgPos != CPos_TRAP); } int CRcvBuffer::getRcvDataSize() const { - return offPos(m_iStartPos, m_iFirstNonreadPos); + //return offPos(m_iStartPos, m_iFirstNonreadPos); + return (m_iFirstNonreadPos - m_iStartPos) VALUE; } int CRcvBuffer::getTimespan_ms() const @@ -958,7 +998,8 @@ int CRcvBuffer::getTimespan_ms() const if (m_iMaxPosOff == 0) return 0; - int lastpos = incPos(m_iStartPos, m_iMaxPosOff - 1); + //int lastpos = incPos(m_iStartPos, m_iMaxPosOff - 1); + CPos lastpos = m_iStartPos + (m_iMaxPosOff - COff(1)); // Normally the last position should always be non empty // if TSBPD is enabled (reading out of order is not allowed). // However if decryption of the last packet fails, it may be dropped @@ -966,16 +1007,18 @@ int CRcvBuffer::getTimespan_ms() const SRT_ASSERT(m_entries[lastpos].pUnit != NULL || m_entries[lastpos].status == EntryState_Drop); while (m_entries[lastpos].pUnit == NULL && lastpos != m_iStartPos) { - lastpos = decPos(lastpos); + //lastpos = decPos(lastpos); + --lastpos; } - + if (m_entries[lastpos].pUnit == NULL) return 0; - int startpos = m_iStartPos; + CPos startpos = m_iStartPos; while (m_entries[startpos].pUnit == NULL && startpos != lastpos) { - startpos = incPos(startpos); + //startpos = incPos(startpos); + ++startpos; } if (m_entries[startpos].pUnit == NULL) @@ -1034,8 +1077,10 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstValidPacketInfo() const std::pair CRcvBuffer::getAvailablePacketsRange() const { - const int seqno_last = CSeqNo::incseq(m_iStartSeqNo, offPos(m_iStartPos, m_iFirstNonreadPos)); - return std::pair(m_iStartSeqNo, seqno_last); + //const int seqno_last = CSeqNo::incseq(m_iStartSeqNo, offPos(m_iStartPos, m_iFirstNonreadPos)); + const int nonread_off = (m_iFirstNonreadPos - m_iStartPos) VALUE; + const int seqno_last = (m_iStartSeqNo + nonread_off) VALUE; + return std::pair(m_iStartSeqNo VALUE, seqno_last); } bool CRcvBuffer::isRcvDataReady(time_point time_now) const @@ -1047,7 +1092,7 @@ bool CRcvBuffer::isRcvDataReady(time_point time_now) const return true; SRT_ASSERT((!m_bMessageAPI && m_numNonOrderPackets == 0) || m_bMessageAPI); - return (m_numNonOrderPackets > 0 && m_iFirstNonOrderMsgPos != -1); + return (m_numNonOrderPackets > 0 && m_iFirstNonOrderMsgPos != CPos_TRAP); } if (!haveInorderPackets) @@ -1072,7 +1117,7 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstReadablePacketInfo(time_point time_no return info; } SRT_ASSERT((!m_bMessageAPI && m_numNonOrderPackets == 0) || m_bMessageAPI); - if (m_iFirstNonOrderMsgPos >= 0) + if (m_iFirstNonOrderMsgPos != CPos_TRAP) { SRT_ASSERT(m_numNonOrderPackets > 0); const CPacket& packet = packetAt(m_iFirstNonOrderMsgPos); @@ -1107,7 +1152,7 @@ void CRcvBuffer::countBytes(int pkts, int bytes) } } -void CRcvBuffer::releaseUnitInPos(int pos) +void CRcvBuffer::releaseUnitInPos(CPos pos) { CUnit* tmp = m_entries[pos].pUnit; m_entries[pos] = Entry(); // pUnit = NULL; status = Empty @@ -1115,7 +1160,7 @@ void CRcvBuffer::releaseUnitInPos(int pos) m_pUnitQueue->makeUnitFree(tmp); } -bool CRcvBuffer::dropUnitInPos(int pos) +bool CRcvBuffer::dropUnitInPos(CPos pos) { if (!m_entries[pos].pUnit) return false; @@ -1127,7 +1172,7 @@ bool CRcvBuffer::dropUnitInPos(int pos) { --m_numNonOrderPackets; if (pos == m_iFirstNonOrderMsgPos) - m_iFirstNonOrderMsgPos = -1; + m_iFirstNonOrderMsgPos = CPos_TRAP; } releaseUnitInPos(pos); return true; @@ -1135,12 +1180,14 @@ bool CRcvBuffer::dropUnitInPos(int pos) void CRcvBuffer::releaseNextFillerEntries() { - int pos = m_iStartPos; + CPos pos = m_iStartPos; while (m_entries[pos].status == EntryState_Read || m_entries[pos].status == EntryState_Drop) { - m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); + //m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); + ++m_iStartSeqNo; releaseUnitInPos(pos); - pos = incPos(pos); + //pos = incPos(pos); + ++pos; m_iStartPos = pos; --m_iMaxPosOff; if (m_iMaxPosOff < 0) @@ -1154,15 +1201,16 @@ void CRcvBuffer::updateNonreadPos() if (m_iMaxPosOff == 0) return; - const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); // The empty position right after the last valid entry. + //const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); // The empty position right after the last valid entry. + const CPos end_pos = m_iStartPos + m_iMaxPosOff; // The empty position right after the last valid entry. - int pos = m_iFirstNonreadPos; + CPos pos = m_iFirstNonreadPos; while (m_entries[pos].pUnit && m_entries[pos].status == EntryState_Avail) { if (m_bMessageAPI && (packetAt(pos).getMsgBoundary() & PB_FIRST) == 0) break; - for (int i = pos; i != end_pos; i = incPos(i)) + for (CPos i = pos; i != end_pos; ++i) // i = incPos(i)) { if (!m_entries[i].pUnit || m_entries[pos].status != EntryState_Avail) { @@ -1176,7 +1224,8 @@ void CRcvBuffer::updateNonreadPos() // Check PB_LAST only in message mode. if (!m_bMessageAPI || packetAt(i).getMsgBoundary() & PB_LAST) { - m_iFirstNonreadPos = incPos(i); + //m_iFirstNonreadPos = incPos(i); + m_iFirstNonreadPos = i + COff(1); break; } } @@ -1188,9 +1237,9 @@ void CRcvBuffer::updateNonreadPos() } } -int CRcvBuffer::findLastMessagePkt() +CPos CRcvBuffer::findLastMessagePkt() { - for (int i = m_iStartPos; i != m_iFirstNonreadPos; i = incPos(i)) + for (CPos i = m_iStartPos; i != m_iFirstNonreadPos; ++i) //i = incPos(i)) { SRT_ASSERT(m_entries[i].pUnit); @@ -1200,10 +1249,10 @@ int CRcvBuffer::findLastMessagePkt() } } - return -1; + return CPos_TRAP; } -void CRcvBuffer::onInsertNonOrderPacket(int insertPos) +void CRcvBuffer::onInsertNonOrderPacket(CPos insertPos) { if (m_numNonOrderPackets == 0) return; @@ -1214,7 +1263,7 @@ void CRcvBuffer::onInsertNonOrderPacket(int insertPos) // // There might happen that the packet being added precedes the previously found one. // However, it is allowed to re bead out of order, so no need to update the position. - if (m_iFirstNonOrderMsgPos >= 0) + if (m_iFirstNonOrderMsgPos != CPos_TRAP) return; // Just a sanity check. This function is called when a new packet is added. @@ -1233,14 +1282,14 @@ void CRcvBuffer::onInsertNonOrderPacket(int insertPos) const int msgNo = pkt.getMsgSeq(m_bPeerRexmitFlag); // First check last packet, because it is expected to be received last. - const bool hasLast = (boundary & PB_LAST) || (-1 < scanNonOrderMessageRight(insertPos, msgNo)); + const bool hasLast = (boundary & PB_LAST) || (scanNonOrderMessageRight(insertPos, msgNo) != CPos_TRAP); if (!hasLast) return; - const int firstPktPos = (boundary & PB_FIRST) + const CPos firstPktPos = (boundary & PB_FIRST) ? insertPos : scanNonOrderMessageLeft(insertPos, msgNo); - if (firstPktPos < 0) + if (firstPktPos == CPos_TRAP) return; m_iFirstNonOrderMsgPos = firstPktPos; @@ -1249,12 +1298,13 @@ void CRcvBuffer::onInsertNonOrderPacket(int insertPos) bool CRcvBuffer::checkFirstReadableNonOrder() { - if (m_numNonOrderPackets <= 0 || m_iFirstNonOrderMsgPos < 0 || m_iMaxPosOff == 0) + if (m_numNonOrderPackets <= 0 || m_iFirstNonOrderMsgPos == CPos_TRAP || m_iMaxPosOff == COff(0)) return false; - const int endPos = incPos(m_iStartPos, m_iMaxPosOff); + //const int endPos = incPos(m_iStartPos, m_iMaxPosOff); + const CPos endPos = m_iStartPos + m_iMaxPosOff; int msgno = -1; - for (int pos = m_iFirstNonOrderMsgPos; pos != endPos; pos = incPos(pos)) + for (CPos pos = m_iFirstNonOrderMsgPos; pos != endPos; ++pos) // pos = incPos(pos)) { if (!m_entries[pos].pUnit) return false; @@ -1277,7 +1327,7 @@ bool CRcvBuffer::checkFirstReadableNonOrder() void CRcvBuffer::updateFirstReadableNonOrder() { - if (hasReadableInorderPkts() || m_numNonOrderPackets <= 0 || m_iFirstNonOrderMsgPos >= 0) + if (hasReadableInorderPkts() || m_numNonOrderPackets <= 0 || m_iFirstNonOrderMsgPos != CPos_TRAP) return; if (m_iMaxPosOff == 0) @@ -1288,17 +1338,19 @@ void CRcvBuffer::updateFirstReadableNonOrder() // Search further packets to the right. // First check if there are packets to the right. - const int lastPos = (m_iStartPos + m_iMaxPosOff - 1) % m_szSize; + //const int lastPos = (m_iStartPos + m_iMaxPosOff - 1) % m_szSize; + const CPos lastPos = m_iStartPos + m_iMaxPosOff - COff(1); - int posFirst = -1; - int posLast = -1; + CPos posFirst = CPos_TRAP; + CPos posLast = CPos_TRAP; int msgNo = -1; - for (int pos = m_iStartPos; outOfOrderPktsRemain; pos = incPos(pos)) + for (CPos pos = m_iStartPos; outOfOrderPktsRemain; ++pos) //pos = incPos(pos)) { if (!m_entries[pos].pUnit) { - posFirst = posLast = msgNo = -1; + posFirst = posLast = CPos_TRAP; + msgNo = -1; continue; } @@ -1306,7 +1358,8 @@ void CRcvBuffer::updateFirstReadableNonOrder() if (pkt.getMsgOrderFlag()) // Skip in order packet { - posFirst = posLast = msgNo = -1; + posFirst = posLast = CPos_TRAP; + msgNo = -1; continue; } @@ -1321,7 +1374,8 @@ void CRcvBuffer::updateFirstReadableNonOrder() if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { - posFirst = posLast = msgNo = -1; + posFirst = posLast = CPos_TRAP; + msgNo = -1; continue; } @@ -1338,18 +1392,20 @@ void CRcvBuffer::updateFirstReadableNonOrder() return; } -int CRcvBuffer::scanNonOrderMessageRight(const int startPos, int msgNo) const +CPos CRcvBuffer::scanNonOrderMessageRight(const CPos startPos, int msgNo) const { // Search further packets to the right. // First check if there are packets to the right. - const int lastPos = (m_iStartPos + m_iMaxPosOff - 1) % m_szSize; + //const int lastPos = (m_iStartPos + m_iMaxPosOff - 1) % m_szSize; + const CPos lastPos = m_iStartPos + m_iMaxPosOff - COff(1); if (startPos == lastPos) - return -1; + return CPos_TRAP; - int pos = startPos; + CPos pos = startPos; do { - pos = incPos(pos); + //pos = incPos(pos); + ++pos; if (!m_entries[pos].pUnit) break; @@ -1358,7 +1414,7 @@ int CRcvBuffer::scanNonOrderMessageRight(const int startPos, int msgNo) const if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { LOGC(rbuflog.Error, log << "Missing PB_LAST packet for msgNo " << msgNo); - return -1; + return CPos_TRAP; } const PacketBoundary boundary = pkt.getMsgBoundary(); @@ -1366,30 +1422,31 @@ int CRcvBuffer::scanNonOrderMessageRight(const int startPos, int msgNo) const return pos; } while (pos != lastPos); - return -1; + return CPos_TRAP; } -int CRcvBuffer::scanNonOrderMessageLeft(const int startPos, int msgNo) const +CPos CRcvBuffer::scanNonOrderMessageLeft(const CPos startPos, int msgNo) const { // Search preceding packets to the left. // First check if there are packets to the left. if (startPos == m_iStartPos) - return -1; + return CPos_TRAP; - int pos = startPos; + CPos pos = startPos; do { - pos = decPos(pos); + //pos = decPos(pos); + --pos; if (!m_entries[pos].pUnit) - return -1; + return CPos_TRAP; const CPacket& pkt = packetAt(pos); if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { LOGC(rbuflog.Error, log << "Missing PB_FIRST packet for msgNo " << msgNo); - return -1; + return CPos_TRAP; } const PacketBoundary boundary = pkt.getMsgBoundary(); @@ -1397,7 +1454,7 @@ int CRcvBuffer::scanNonOrderMessageLeft(const int startPos, int msgNo) const return pos; } while (pos != m_iStartPos); - return -1; + return CPos_TRAP; } bool CRcvBuffer::addRcvTsbPdDriftSample(uint32_t usTimestamp, const time_point& tsPktArrival, int usRTTSample) @@ -1435,12 +1492,12 @@ void CRcvBuffer::updateTsbPdTimeBase(uint32_t usPktTimestamp) m_tsbpd.updateTsbPdTimeBase(usPktTimestamp); } -string CRcvBuffer::strFullnessState(int iFirstUnackSeqNo, const time_point& tsNow) const +string CRcvBuffer::strFullnessState(int32_t iFirstUnackSeqNo, const time_point& tsNow) const { stringstream ss; - ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo - << " m_iStartPos=" << m_iStartPos << " m_iMaxPosOff=" << m_iMaxPosOff << ". "; + ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo VALUE + << " m_iStartPos=" << m_iStartPos VALUE << " m_iMaxPosOff=" << m_iMaxPosOff VALUE << ". "; ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << m_szSize << " pkts. "; @@ -1451,7 +1508,7 @@ string CRcvBuffer::strFullnessState(int iFirstUnackSeqNo, const time_point& tsNo if (!is_zero(nextValidPkt.tsbpd_time)) { ss << count_milliseconds(nextValidPkt.tsbpd_time - tsNow) << "ms"; - const int iLastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); + const CPos iLastPos = m_iStartPos + m_iMaxPosOff - COff(1); //incPos(m_iStartPos, m_iMaxPosOff - 1); if (m_entries[iLastPos].pUnit) { ss << ", timespan "; @@ -1502,35 +1559,39 @@ void CRcvBuffer::updRcvAvgDataSize(const steady_clock::time_point& now) int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) { - int offset = CSeqNo::seqoff(m_iStartSeqNo, fromseq); + //int offset = CSeqNo::seqoff(m_iStartSeqNo, fromseq); + int offset_val = CSeqNo(fromseq) - m_iStartSeqNo; + COff offset (offset_val); // Check if it's still inside the buffer - if (offset < 0 || offset >= m_iMaxPosOff) + if (offset_val < 0 || offset >= m_iMaxPosOff) { - HLOGC(rbuflog.Debug, log << "getFirstLossSeq: offset=" << offset << " for %" << fromseq - << " (with max=" << m_iMaxPosOff << ") - NO LOSS FOUND"); + HLOGC(rbuflog.Debug, log << "getFirstLossSeq: offset=" << offset VALUE << " for %" << fromseq + << " (with max=" << m_iMaxPosOff VALUE << ") - NO LOSS FOUND"); return SRT_SEQNO_NONE; } // Start position - int frompos = incPos(m_iStartPos, offset); + //int frompos = incPos(m_iStartPos, offset); + CPos frompos = m_iStartPos + offset; // Ok; likely we should stand at the m_iEndPos position. // If this given position is earlier than this, then // m_iEnd stands on the first loss, unless it's equal // to the position pointed by m_iMaxPosOff. - int32_t ret_seq = SRT_SEQNO_NONE; - int ret_off = m_iMaxPosOff; + CSeqNo ret_seq = CSeqNo(SRT_SEQNO_NONE); + COff ret_off = m_iMaxPosOff; - int end_off = offPos(m_iStartPos, m_iEndPos); - if (frompos < end_off) + COff end_off = m_iEndPos - m_iStartPos; //offPos(m_iStartPos, m_iEndPos); + if (offset < end_off) { // If m_iEndPos has such a value, then there are // no loss packets at all. if (end_off != m_iMaxPosOff) { - ret_seq = CSeqNo::incseq(m_iStartSeqNo, end_off); + //ret_seq = CSeqNo::incseq(m_iStartSeqNo, end_off); + ret_seq = m_iStartSeqNo + end_off VALUE; ret_off = end_off; } } @@ -1544,11 +1605,13 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) // REUSE offset as a control variable for (; offset < m_iMaxPosOff; ++offset) { - int pos = incPos(m_iStartPos, offset); + //int pos = incPos(m_iStartPos, offset); + const CPos pos = m_iStartPos + offset; if (m_entries[pos].status == EntryState_Empty) { ret_off = offset; - ret_seq = CSeqNo::incseq(m_iStartSeqNo, offset); + //ret_seq = CSeqNo::incseq(m_iStartSeqNo, offset); + ret_seq = m_iStartSeqNo + offset VALUE; break; } } @@ -1559,26 +1622,31 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) // Also no need to search anything if only the beginning was // being looked for. - if (ret_seq == SRT_SEQNO_NONE || !pw_end) - return ret_seq; + if (ret_seq == CSeqNo(SRT_SEQNO_NONE) || !pw_end) + return ret_seq VALUE; // We want also the end range, so continue from where you // stopped. // Start from ret_off + 1 because we know already that ret_off // points to an empty cell. - for (int off = ret_off + 1; off < m_iMaxPosOff; ++off) + for (COff off = ret_off + COff(1); off < m_iMaxPosOff; ++off) { - int pos = incPos(m_iStartPos, off); + //int pos = incPos(m_iStartPos, off); + const CPos pos = m_iStartPos + off; if (m_entries[pos].status != EntryState_Empty) { - *pw_end = CSeqNo::incseq(m_iStartSeqNo, off - 1); - return ret_seq; + //*pw_end = CSeqNo::incseq(m_iStartSeqNo, off - 1); + *pw_end = (m_iStartSeqNo + (off - COff(1)) VALUE) VALUE; + return ret_seq VALUE; } } // Fallback - this should be impossible, so issue a log. - LOGC(rbuflog.Error, log << "IPE: empty cell pos=" << frompos << " %" << CSeqNo::incseq(m_iStartSeqNo, ret_off) << " not followed by any valid cell"); + LOGC(rbuflog.Error, log << "IPE: empty cell pos=" << frompos VALUE << " %" + //<< CSeqNo::incseq(m_iStartSeqNo, ret_off) + << (m_iStartSeqNo + ret_off) VALUE + << " not followed by any valid cell"); // Return this in the last resort - this could only be a situation when // a packet has somehow disappeared, but it contains empty cells up to the diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 24f5066f6..18450947d 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -15,10 +15,162 @@ #include "common.h" #include "queue.h" #include "tsbpd_time.h" +#include "utilities.h" + +#define USE_WRAPPERS 1 +#define USE_OPERATORS 1 namespace srt { +// DEVELOPMENT TOOL - TO BE MOVED ELSEWHERE (like common.h) + +#if USE_WRAPPERS +struct CPos +{ + int value; + const size_t* psize; + + int isize() const {return *psize;} + + explicit CPos(explicit_t ps, explicit_t val): value(val), psize(ps) {} + int val() const { return value; } + explicit operator int() const {return value;} + + CPos(const CPos& src): value(src.value), psize(src.psize) {} + CPos& operator=(const CPos& src) + { + psize = src.psize; + value = src.value; + return *this; + } + + int cmp(CPos other, CPos start) const + { + int pos2 = value; + int pos1 = other.value; + + const int off1 = pos1 >= start.value ? pos1 - start.value : pos1 + start.isize() - start.value; + const int off2 = pos2 >= start.value ? pos2 - start.value : pos2 + start.isize() - start.value; + + return off2 - off1; + } + + CPos& operator--() + { + if (value == 0) + value = isize() - 1; + else + --value; + return *this; + } + + CPos& operator++() + { + ++value; + if (value == isize()) + value = 0; + return *this; + } + + bool operator == (CPos other) const { return value == other.value; } + bool operator != (CPos other) const { return value != other.value; } +}; + +struct COff +{ + int value; + explicit COff(int v): value(v) {} + COff& operator=(int v) { value = v; return *this; } + + int val() const { return value; } + explicit operator int() const {return value;} + + COff& operator--() { --value; return *this; } + COff& operator++() { ++value; return *this; } + + COff operator--(int) { int v = value; --value; return COff(v); } + COff operator++(int) { int v = value; ++value; return COff(v); } + + COff operator+(COff other) const { return COff(value + other.value); } + COff operator-(COff other) const { return COff(value - other.value); } + COff& operator+=(COff other) { value += other.value; return *this;} + COff& operator-=(COff other) { value -= other.value; return *this;} + + bool operator == (COff other) const { return value == other.value; } + bool operator != (COff other) const { return value != other.value; } + bool operator < (COff other) const { return value < other.value; } + bool operator > (COff other) const { return value > other.value; } + bool operator <= (COff other) const { return value <= other.value; } + bool operator >= (COff other) const { return value >= other.value; } + + // Exceptionally allow modifications of COff by a bare integer + COff operator+(int other) const { return COff(value + other); } + COff operator-(int other) const { return COff(value - other); } + COff& operator+=(int other) { value += other; return *this;} + COff& operator-=(int other) { value -= other; return *this;} + + bool operator == (int other) const { return value == other; } + bool operator != (int other) const { return value != other; } + bool operator < (int other) const { return value < other; } + bool operator > (int other) const { return value > other; } + bool operator <= (int other) const { return value <= other; } + bool operator >= (int other) const { return value >= other; } +}; + +#define VALUE .val() + +#if USE_OPERATORS + +inline CPos operator+(const CPos& pos, COff off) +{ + int val = pos.value + off.value; + while (val >= pos.isize()) + val -= pos.isize(); + return CPos(pos.psize, val); +} + +inline CPos operator-(const CPos& pos, COff off) +{ + int val = pos.value - off.value; + while (val < 0) + val += pos.isize(); + return CPos(pos.psize, val); +} + +// Should verify that CPos use the same size! +inline COff operator-(CPos later, CPos earlier) +{ + if (later.value < earlier.value) + return COff(later.value + later.isize() - earlier.value); + + return COff(later.value - earlier.value); +} + +inline CSeqNo operator+(CSeqNo seq, COff off) +{ + int32_t val = CSeqNo::incseq(seq.val(), off.val()); + return CSeqNo(val); +} + +inline CSeqNo operator-(CSeqNo seq, COff off) +{ + int32_t val = CSeqNo::decseq(seq.val(), off.val()); + return CSeqNo(val); +} + + +#endif +const size_t fix_rollover = 16; +const CPos CPos_TRAP (&fix_rollover, -1); + +#else +#define VALUE +typedef int CPos; +typedef int COff; +const int CPos_TRAP = -1; +#endif + // // Circular receiver buffer. // @@ -229,9 +381,9 @@ class CRcvBuffer // Below fields are valid only if result == INSERTED. Otherwise they have trap repro. - int first_seq; // sequence of the first available readable packet + CSeqNo first_seq; // sequence of the first available readable packet time_point first_time; // Time of the new, earlier packet that appeared ready, or null-time if this didn't change. - int avail_range; + COff avail_range; InsertInfo(Result r, int fp_seq = SRT_SEQNO_NONE, int range = 0, time_point fp_time = time_point()) @@ -265,8 +417,8 @@ class CRcvBuffer /// InsertInfo insert(CUnit* unit); - time_point updatePosInfo(const CUnit* unit, const int prev_max_off, const int newpktpos, const bool extended_end); - const CPacket* tryAvailPacketAt(int pos, int& w_span); + time_point updatePosInfo(const CUnit* unit, const COff prev_max_off, const CPos newpktpos, const bool extended_end); + const CPacket* tryAvailPacketAt(CPos pos, COff& w_span); void getAvailInfo(InsertInfo& w_if); /// Update the values of `m_iEndPos` and `m_iDropPos` in @@ -289,7 +441,7 @@ class CRcvBuffer /// /// @param prev_max_pos buffer position represented by `m_iMaxPosOff` /// - void updateGapInfo(int prev_max_pos); + void updateGapInfo(CPos prev_max_pos); /// Drop packets in the receiver buffer from the current position up to the seqno (excluding seqno). /// @param [in] seqno drop units up to this sequence number @@ -362,31 +514,32 @@ class CRcvBuffer public: /// Get the starting position of the buffer as a packet sequence number. - int getStartSeqNo() const { return m_iStartSeqNo; } + int getStartSeqNo() const { return m_iStartSeqNo VALUE; } /// Sets the start seqno of the buffer. /// Must be used with caution and only when the buffer is empty. - void setStartSeqNo(int seqno) { m_iStartSeqNo = seqno; } + void setStartSeqNo(int seqno) { m_iStartSeqNo = CSeqNo(seqno); } /// Given the sequence number of the first unacknowledged packet /// tells the size of the buffer available for packets. /// Effective returns capacity of the buffer minus acknowledged packet still kept in it. // TODO: Maybe does not need to return minus one slot now to distinguish full and empty buffer. - size_t getAvailSize(int iFirstUnackSeqNo) const + size_t getAvailSize(int32_t iFirstUnackSeqNo) const { // Receiver buffer allows reading unacknowledged packets. // Therefore if the first packet in the buffer is ahead of the iFirstUnackSeqNo // then it does not have acknowledged packets and its full capacity is available. // Otherwise subtract the number of acknowledged but not yet read packets from its capacity. - const int iRBufSeqNo = getStartSeqNo(); - if (CSeqNo::seqcmp(iRBufSeqNo, iFirstUnackSeqNo) >= 0) // iRBufSeqNo >= iFirstUnackSeqNo + const CSeqNo iRBufSeqNo = m_iStartSeqNo; + //if (CSeqNo::seqcmp(iRBufSeqNo, iFirstUnackSeqNo) >= 0) // iRBufSeqNo >= iFirstUnackSeqNo + if (iRBufSeqNo >= CSeqNo(iFirstUnackSeqNo)) { // Full capacity is available. return capacity(); } // Note: CSeqNo::seqlen(n, n) returns 1. - return capacity() - CSeqNo::seqlen(iRBufSeqNo, iFirstUnackSeqNo) + 1; + return capacity() - CSeqNo::seqlen(iRBufSeqNo VALUE, iFirstUnackSeqNo) + 1; } /// @brief Checks if the buffer has packets available for reading regardless of the TSBPD. @@ -436,7 +589,7 @@ class CRcvBuffer bool empty() const { - return (m_iMaxPosOff == 0); + return (m_iMaxPosOff == COff(0)); } /// Return buffer capacity. @@ -453,7 +606,7 @@ class CRcvBuffer /// between the initial position and the youngest received packet. size_t size() const { - return m_iMaxPosOff; + return size_t(m_iMaxPosOff VALUE); } // Returns true if the buffer is full. Requires locking. @@ -489,6 +642,7 @@ class CRcvBuffer const CUnit* peek(int32_t seqno); private: + /* inline int incPos(int pos, int inc = 1) const { return (pos + inc) % m_szSize; } inline int decPos(int pos) const { return (pos - 1) >= 0 ? (pos - 1) : int(m_szSize - 1); } inline int offPos(int pos1, int pos2) const { return (pos2 >= pos1) ? (pos2 - pos1) : int(m_szSize + pos2 - pos1); } @@ -506,20 +660,21 @@ class CRcvBuffer return off2 - off1; } + */ // NOTE: Assumes that pUnit != NULL - CPacket& packetAt(int pos) { return m_entries[pos].pUnit->m_Packet; } - const CPacket& packetAt(int pos) const { return m_entries[pos].pUnit->m_Packet; } + CPacket& packetAt(CPos pos) { return m_entries[pos].pUnit->m_Packet; } + const CPacket& packetAt(CPos pos) const { return m_entries[pos].pUnit->m_Packet; } private: void countBytes(int pkts, int bytes); void updateNonreadPos(); - void releaseUnitInPos(int pos); + void releaseUnitInPos(CPos pos); /// @brief Drop a unit from the buffer. /// @param pos position in the m_entries of the unit to drop. /// @return false if nothing to drop, true if the unit was dropped successfully. - bool dropUnitInPos(int pos); + bool dropUnitInPos(CPos pos); /// Release entries following the current buffer position if they were already /// read out of order (EntryState_Read) or dropped (EntryState_Drop). @@ -528,15 +683,15 @@ class CRcvBuffer bool hasReadableInorderPkts() const { return (m_iFirstNonreadPos != m_iStartPos); } /// Find position of the last packet of the message. - int findLastMessagePkt(); + CPos findLastMessagePkt(); /// Scan for availability of out of order packets. - void onInsertNonOrderPacket(int insertpos); + void onInsertNonOrderPacket(CPos insertpos); // Check if m_iFirstNonOrderMsgPos is still readable. bool checkFirstReadableNonOrder(); void updateFirstReadableNonOrder(); - int scanNonOrderMessageRight(int startPos, int msgNo) const; - int scanNonOrderMessageLeft(int startPos, int msgNo) const; + CPos scanNonOrderMessageRight(CPos startPos, int msgNo) const; + CPos scanNonOrderMessageLeft(CPos startPos, int msgNo) const; typedef bool copy_to_dst_f(char* data, int len, int dst_offset, void* arg); @@ -588,18 +743,18 @@ class CRcvBuffer //static Entry emptyEntry() { return Entry { NULL, EntryState_Empty }; } - typedef FixedArray entries_t; + 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 - int m_iStartSeqNo; - int m_iStartPos; // the head position for I/O (inclusive) - int m_iEndPos; // past-the-end of the contiguous region since m_iStartPos - int m_iDropPos; // points past m_iEndPos to the first deliverable after a gap, or == m_iEndPos if no such packet - int m_iFirstNonreadPos; // First position that can't be read (<= m_iLastAckPos) - int m_iMaxPosOff; // the furthest data position + CSeqNo m_iStartSeqNo; + CPos m_iStartPos; // the head position for I/O (inclusive) + CPos m_iEndPos; // past-the-end of the contiguous region since m_iStartPos + CPos m_iDropPos; // points past m_iEndPos to the first deliverable after a gap, or == m_iEndPos if no such packet + CPos m_iFirstNonreadPos; // First position that can't be read (<= m_iLastAckPos) + COff m_iMaxPosOff; // the furthest data position int m_iNotch; // index of the first byte to read in the first ready-to-read packet (used in file/stream mode) size_t m_numNonOrderPackets; // The number of stored packets with "inorder" flag set to false @@ -607,7 +762,7 @@ class CRcvBuffer /// Points to the first packet of a message that has out-of-order flag /// and is complete (all packets from first to last are in the buffer). /// If there is no such message in the buffer, it contains -1. - int m_iFirstNonOrderMsgPos; + CPos m_iFirstNonOrderMsgPos; bool m_bPeerRexmitFlag; // Needed to read message number correctly const bool m_bMessageAPI; // Operation mode flag: message or stream. @@ -637,7 +792,7 @@ class CRcvBuffer /// Form a string of the current buffer fullness state. /// number of packets acknowledged, TSBPD readiness, etc. - std::string strFullnessState(int iFirstUnackSeqNo, const time_point& tsNow) const; + std::string strFullnessState(int32_t iFirstUnackSeqNo, const time_point& tsNow) const; private: CTsbpdTime m_tsbpd; diff --git a/srtcore/common.h b/srtcore/common.h index 6a8912118..1c15dd01a 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -580,6 +580,8 @@ class CSeqNo explicit CSeqNo(int32_t v): value(v) {} + int32_t val() const { return value; } + // Comparison bool operator == (const CSeqNo& other) const { return other.value == value; } bool operator < (const CSeqNo& other) const diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 45aeb2b4c..ad42ceb92 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -8192,24 +8192,12 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) log << CONID() << "ACK: clip %" << m_iRcvLastAck << "-%" << ack << ", REVOKED " << CSeqNo::seqoff(ack, m_iRcvLastAck) << " from RCV buffer"); - if (m_bTsbPd) - { - /* - There's no need to update TSBPD in the wake-on-recv state - from ACK because it is being done already in the receiver thread - when a newly inserted packet caused provision of a new candidate - that could be delivered soon. Also, this flag is only used in TSBPD - mode and can be only set to true in the TSBPD thread. - - // Newly acknowledged data, signal TsbPD thread // - CUniqueSync tslcc (m_RecvLock, m_RcvTsbPdCond); - // m_bWakeOnRecv is protected by m_RecvLock in the tsbpd() thread - if (m_bWakeOnRecv) - tslcc.notify_one(); - - */ - } - else + // There's no need to update TSBPD in the wake-on-recv state + // from ACK because it is being done already in the receiver thread + // when a newly inserted packet caused provision of a new candidate + // that could be delivered soon. Also, this flag is only used in TSBPD + // mode and can be only set to true in the TSBPD thread. + if (!m_bTsbPd) { { CUniqueSync rdcc (m_RecvLock, m_RecvDataCond); @@ -10682,14 +10670,6 @@ int srt::CUDT::processData(CUnit* in_unit) { HLOGC(qrlog.Debug, log << CONID() << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs)); sendLossReport(filter_loss_seqs); - - // XXX unsure as to whether this should change anything in the TSBPD conditions. - // Might be that this trigger is not necessary. - if (m_bTsbPd) - { - HLOGC(qrlog.Debug, log << CONID() << "loss: signaling TSBPD cond"); - CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); - } } // Now review the list of FreshLoss to see if there's any "old enough" to send UMSG_LOSSREPORT to it. diff --git a/srtcore/utilities.h b/srtcore/utilities.h index 8a1374eb7..9dbebe762 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -414,7 +414,7 @@ struct DynamicStruct /// Fixed-size array template class. namespace srt { -template +template class FixedArray { public: @@ -430,22 +430,23 @@ class FixedArray } public: - const T& operator[](size_t index) const + const T& operator[](Indexer index) const { - if (index >= m_size) - throw_invalid_index(index); + if (int(index) >= int(m_size)) + throw_invalid_index(int(index)); - return m_entries[index]; + return m_entries[int(index)]; } - T& operator[](size_t index) + T& operator[](Indexer index) { - if (index >= m_size) - throw_invalid_index(index); + if (int(index) >= int(m_size)) + throw_invalid_index(int(index)); - return m_entries[index]; + return m_entries[int(index)]; } + /* const T& operator[](int index) const { if (index < 0 || static_cast(index) >= m_size) @@ -461,6 +462,7 @@ class FixedArray return m_entries[index]; } + */ size_t size() const { return m_size; } From ab469d65d58a9e4ec825d4557bc405f70e8abb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 10 Jun 2024 11:09:06 +0200 Subject: [PATCH 55/62] Fixed previous problems with receiver buffer --- srtcore/buffer_rcv.cpp | 168 +++++++++++++++++++---------------------- srtcore/buffer_rcv.h | 61 ++++++++++++--- 2 files changed, 129 insertions(+), 100 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index ab72670df..539c51f9e 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -75,23 +75,6 @@ namespace { #define IF_RCVBUF_DEBUG(instr) (void)0 - // 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. - bool isInRange(CPos iStartPos, COff iMaxPosOff, size_t iSize, CPos iFirstNonreadPos) - { - if (iFirstNonreadPos == iStartPos) - return true; - - //const int iLastPos = (iStartPos VALUE + iMaxPosOff VALUE) % iSize; - const CPos iLastPos = iStartPos + iMaxPosOff; - const bool isOverrun = iLastPos VALUE < iStartPos VALUE; - - if (isOverrun) - return iFirstNonreadPos VALUE > iStartPos VALUE || iFirstNonreadPos VALUE <= iLastPos VALUE; - - return iFirstNonreadPos VALUE > iStartPos VALUE && iFirstNonreadPos VALUE <= iLastPos VALUE; - } } @@ -194,9 +177,10 @@ 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) VALUE / m_szSize < 2); + SRT_ASSERT((m_iStartPos VALUE + offset VALUE) / m_szSize < 2); - const CPos newpktpos = m_iStartPos + offset; + //const CPos newpktpos = m_iStartPos + offset; + const CPos newpktpos = incPos(m_iStartPos, offset); const COff prev_max_off = m_iMaxPosOff; bool extended_end = false; if (offset >= m_iMaxPosOff) @@ -287,8 +271,8 @@ const CPacket* CRcvBuffer::tryAvailPacketAt(CPos pos, COff& w_span) if (m_entries[m_iStartPos].status == EntryState_Avail) { pos = m_iStartPos; - //w_span = offPos(m_iStartPos, m_iEndPos); - w_span = m_iEndPos - m_iStartPos; + w_span = offPos(m_iStartPos, m_iEndPos); + //w_span = m_iEndPos - m_iStartPos; } if (pos == CPos_TRAP) @@ -312,8 +296,8 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff p { time_point earlier_time; - //int prev_max_pos = incPos(m_iStartPos, prev_max_off); - CPos prev_max_pos = m_iStartPos + prev_max_off; + CPos prev_max_pos = incPos(m_iStartPos, prev_max_off); + //CPos prev_max_pos = m_iStartPos + prev_max_off; // Update flags // Case [A] @@ -328,8 +312,8 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff p // This means that m_iEndPos now shifts by 1, // and m_iDropPos must be shifted together with it, // as there's no drop to point. - //m_iEndPos = incPos(m_iStartPos, m_iMaxPosOff); - m_iEndPos = m_iStartPos + m_iMaxPosOff; + m_iEndPos = incPos(m_iStartPos, m_iMaxPosOff); + //m_iEndPos = m_iStartPos + m_iMaxPosOff; m_iDropPos = m_iEndPos; } else @@ -337,8 +321,8 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff p // Otherwise we have a drop-after-gap candidate // which is the currently inserted packet. // Therefore m_iEndPos STAYS WHERE IT IS. - //m_iDropPos = incPos(m_iStartPos, m_iMaxPosOff - 1); - m_iDropPos = m_iStartPos + (m_iMaxPosOff - 1); + m_iDropPos = incPos(m_iStartPos, m_iMaxPosOff - 1); + //m_iDropPos = m_iStartPos + (m_iMaxPosOff - 1); } } } @@ -483,12 +467,12 @@ int CRcvBuffer::dropUpTo(int32_t seqno) // Start from here and search fort the next gap m_iEndPos = m_iDropPos = m_iStartPos; - //updateGapInfo(incPos(m_iStartPos, m_iMaxPosOff)); - updateGapInfo(m_iStartPos + m_iMaxPosOff); + updateGapInfo(incPos(m_iStartPos, m_iMaxPosOff)); + //updateGapInfo(m_iStartPos + m_iMaxPosOff); // If the nonread position is now behind the starting position, set it to the starting position and update. // Preceding packets were likely missing, and the non read position can probably be moved further now. - if (!isInRange(m_iStartPos, m_iMaxPosOff, m_szSize, m_iFirstNonreadPos)) + if (!isInUsedRange( m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; updateNonreadPos(); @@ -533,13 +517,13 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro COff minDroppedOffset = COff(-1); int iDropCnt = 0; const COff start_off = COff(max(0, offset_a)); - //const int start_pos = incPos(m_iStartPos, start_off); - const CPos start_pos = m_iStartPos + start_off; + const CPos start_pos = incPos(m_iStartPos, start_off); + //const CPos start_pos = m_iStartPos + start_off; const COff end_off = COff(min((int) m_szSize - 1, offset_b + 1)); - //const int end_pos = incPos(m_iStartPos, end_off); - const CPos end_pos = m_iStartPos + end_off; + const CPos end_pos = incPos(m_iStartPos, end_off); + //const CPos end_pos = 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) + for (CPos i = start_pos; i != end_pos; i = incPos(i)) // ++i) { // Check if the unit was already dropped earlier. if (m_entries[i].status == EntryState_Drop) @@ -577,8 +561,8 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro ++iDropCnt; m_entries[i].status = EntryState_Drop; if (minDroppedOffset == -1) - //minDroppedOffset = offPos(m_iStartPos, i); - minDroppedOffset = i - m_iStartPos; + minDroppedOffset = offPos(m_iStartPos, i); + //minDroppedOffset = i - m_iStartPos; } if (bDropByMsgNo) @@ -588,8 +572,8 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro // The sender should have the last packet of the message it is requesting to be dropped. // Therefore we don't search forward, but need to check earlier packets in the RCV buffer. // Try to drop by the message number in case the message starts earlier than @a seqnolo. - //const int stop_pos = decPos(m_iStartPos); - const CPos stop_pos = m_iStartPos - COff(1); + const CPos stop_pos = decPos(m_iStartPos); + //const CPos stop_pos = m_iStartPos - COff(1); for (CPos i = start_pos; i != stop_pos; --i) { // Can't drop if message number is not known. @@ -613,8 +597,8 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro dropUnitInPos(i); m_entries[i].status = EntryState_Drop; // As the search goes backward, i is always earlier than minDroppedOffset. - //minDroppedOffset = offPos(m_iStartPos, i); - minDroppedOffset = i - m_iStartPos; + minDroppedOffset = offPos(m_iStartPos, i); + //minDroppedOffset = i - m_iStartPos; // Break the loop if the start of the message has been found. No need to search further. if (bnd == PB_FIRST) @@ -660,8 +644,8 @@ bool CRcvBuffer::getContiguousEnd(int32_t& w_seq) const return m_iMaxPosOff > 0; } - //int end_off = offPos(m_iStartPos, m_iEndPos); - COff end_off = m_iEndPos - m_iStartPos; + COff end_off = offPos(m_iStartPos, m_iEndPos); + //COff end_off = m_iEndPos - m_iStartPos; //w_seq = CSeqNo::incseq(m_iStartSeqNo, end_off); w_seq = (m_iStartSeqNo + end_off VALUE) VALUE; @@ -745,7 +729,8 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair= remain_pktlen) { releaseUnitInPos(p); - //p = incPos(p); - ++p; + p = incPos(p); + //++p; m_iNotch = 0; m_iStartPos = p; @@ -954,7 +939,7 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) // Update positions // Set nonread position to the starting position before updating, // because start position was increased, and preceding packets are invalid. - if (!isInRange(m_iStartPos, m_iMaxPosOff, m_szSize, m_iFirstNonreadPos)) + if (!isInUsedRange( m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; } @@ -986,8 +971,8 @@ bool CRcvBuffer::hasAvailablePackets() const int CRcvBuffer::getRcvDataSize() const { - //return offPos(m_iStartPos, m_iFirstNonreadPos); - return (m_iFirstNonreadPos - m_iStartPos) VALUE; + return offPos(m_iStartPos, m_iFirstNonreadPos) VALUE; + //return (m_iFirstNonreadPos - m_iStartPos) VALUE; } int CRcvBuffer::getTimespan_ms() const @@ -998,8 +983,9 @@ int CRcvBuffer::getTimespan_ms() const if (m_iMaxPosOff == 0) return 0; - //int lastpos = incPos(m_iStartPos, m_iMaxPosOff - 1); - CPos lastpos = m_iStartPos + (m_iMaxPosOff - COff(1)); + CPos lastpos = incPos(m_iStartPos, m_iMaxPosOff - 1); + //CPos lastpos = m_iStartPos + (m_iMaxPosOff - COff(1)); + // Normally the last position should always be non empty // if TSBPD is enabled (reading out of order is not allowed). // However if decryption of the last packet fails, it may be dropped @@ -1007,8 +993,8 @@ int CRcvBuffer::getTimespan_ms() const SRT_ASSERT(m_entries[lastpos].pUnit != NULL || m_entries[lastpos].status == EntryState_Drop); while (m_entries[lastpos].pUnit == NULL && lastpos != m_iStartPos) { - //lastpos = decPos(lastpos); - --lastpos; + lastpos = decPos(lastpos); + //--lastpos; } if (m_entries[lastpos].pUnit == NULL) @@ -1017,8 +1003,8 @@ int CRcvBuffer::getTimespan_ms() const CPos startpos = m_iStartPos; while (m_entries[startpos].pUnit == NULL && startpos != lastpos) { - //startpos = incPos(startpos); - ++startpos; + startpos = incPos(startpos); + //++startpos; } if (m_entries[startpos].pUnit == NULL) @@ -1077,9 +1063,10 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstValidPacketInfo() const std::pair CRcvBuffer::getAvailablePacketsRange() const { - //const int seqno_last = CSeqNo::incseq(m_iStartSeqNo, offPos(m_iStartPos, m_iFirstNonreadPos)); - const int nonread_off = (m_iFirstNonreadPos - m_iStartPos) VALUE; - const int seqno_last = (m_iStartSeqNo + nonread_off) VALUE; + const COff nonread_off = offPos(m_iStartPos, m_iFirstNonreadPos); + const int seqno_last = CSeqNo::incseq(m_iStartSeqNo VALUE, nonread_off VALUE); + //const int nonread_off = (m_iFirstNonreadPos - m_iStartPos) VALUE; + //const int seqno_last = (m_iStartSeqNo + nonread_off) VALUE; return std::pair(m_iStartSeqNo VALUE, seqno_last); } @@ -1201,8 +1188,8 @@ void CRcvBuffer::updateNonreadPos() if (m_iMaxPosOff == 0) return; - //const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); // The empty position right after the last valid entry. - const CPos end_pos = m_iStartPos + m_iMaxPosOff; // The empty position right after the last valid entry. + const CPos end_pos = incPos(m_iStartPos, m_iMaxPosOff); // The empty position right after the last valid entry. + //const CPos end_pos = m_iStartPos + m_iMaxPosOff; // The empty position right after the last valid entry. CPos pos = m_iFirstNonreadPos; while (m_entries[pos].pUnit && m_entries[pos].status == EntryState_Avail) @@ -1224,8 +1211,8 @@ void CRcvBuffer::updateNonreadPos() // Check PB_LAST only in message mode. if (!m_bMessageAPI || packetAt(i).getMsgBoundary() & PB_LAST) { - //m_iFirstNonreadPos = incPos(i); - m_iFirstNonreadPos = i + COff(1); + m_iFirstNonreadPos = incPos(i); + //m_iFirstNonreadPos = i + COff(1); break; } } @@ -1301,10 +1288,10 @@ bool CRcvBuffer::checkFirstReadableNonOrder() if (m_numNonOrderPackets <= 0 || m_iFirstNonOrderMsgPos == CPos_TRAP || m_iMaxPosOff == COff(0)) return false; - //const int endPos = incPos(m_iStartPos, m_iMaxPosOff); - const CPos endPos = m_iStartPos + m_iMaxPosOff; + const CPos endPos = incPos(m_iStartPos, m_iMaxPosOff); + //const CPos endPos = m_iStartPos + m_iMaxPosOff; int msgno = -1; - for (CPos pos = m_iFirstNonOrderMsgPos; pos != endPos; ++pos) // pos = incPos(pos)) + for (CPos pos = m_iFirstNonOrderMsgPos; pos != endPos; pos = incPos(pos)) // ++pos) { if (!m_entries[pos].pUnit) return false; @@ -1339,7 +1326,8 @@ void CRcvBuffer::updateFirstReadableNonOrder() // Search further packets to the right. // First check if there are packets to the right. //const int lastPos = (m_iStartPos + m_iMaxPosOff - 1) % m_szSize; - const CPos lastPos = m_iStartPos + m_iMaxPosOff - COff(1); + //const CPos lastPos = m_iStartPos + m_iMaxPosOff - COff(1); + const CPos lastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); CPos posFirst = CPos_TRAP; CPos posLast = CPos_TRAP; @@ -1397,7 +1385,8 @@ CPos CRcvBuffer::scanNonOrderMessageRight(const CPos startPos, int msgNo) const // Search further packets to the right. // First check if there are packets to the right. //const int lastPos = (m_iStartPos + m_iMaxPosOff - 1) % m_szSize; - const CPos lastPos = m_iStartPos + m_iMaxPosOff - COff(1); + //const CPos lastPos = m_iStartPos + m_iMaxPosOff - COff(1); + const CPos lastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); if (startPos == lastPos) return CPos_TRAP; @@ -1508,7 +1497,8 @@ string CRcvBuffer::strFullnessState(int32_t iFirstUnackSeqNo, const time_point& if (!is_zero(nextValidPkt.tsbpd_time)) { ss << count_milliseconds(nextValidPkt.tsbpd_time - tsNow) << "ms"; - const CPos iLastPos = m_iStartPos + m_iMaxPosOff - COff(1); //incPos(m_iStartPos, m_iMaxPosOff - 1); + const CPos iLastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); + //const CPos iLastPos = m_iStartPos + m_iMaxPosOff - COff(1); if (m_entries[iLastPos].pUnit) { ss << ", timespan "; @@ -1572,8 +1562,8 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) } // Start position - //int frompos = incPos(m_iStartPos, offset); - CPos frompos = m_iStartPos + offset; + CPos frompos = incPos(m_iStartPos, offset); + //CPos frompos = m_iStartPos + offset; // Ok; likely we should stand at the m_iEndPos position. // If this given position is earlier than this, then @@ -1582,15 +1572,15 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) CSeqNo ret_seq = CSeqNo(SRT_SEQNO_NONE); COff ret_off = m_iMaxPosOff; - - COff end_off = m_iEndPos - m_iStartPos; //offPos(m_iStartPos, m_iEndPos); + COff end_off = offPos(m_iStartPos, m_iEndPos); + //COff end_off = m_iEndPos - m_iStartPos; if (offset < end_off) { // If m_iEndPos has such a value, then there are // no loss packets at all. if (end_off != m_iMaxPosOff) { - //ret_seq = CSeqNo::incseq(m_iStartSeqNo, end_off); + //ret_seq = CSeqNo::incseq(m_iStartSeqNo VALUE, end_off VALUE); ret_seq = m_iStartSeqNo + end_off VALUE; ret_off = end_off; } @@ -1605,8 +1595,8 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) // REUSE offset as a control variable for (; offset < m_iMaxPosOff; ++offset) { - //int pos = incPos(m_iStartPos, offset); - const CPos pos = m_iStartPos + offset; + const CPos pos = incPos(m_iStartPos, offset); + //const CPos pos = m_iStartPos + offset; if (m_entries[pos].status == EntryState_Empty) { ret_off = offset; @@ -1632,11 +1622,11 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) // points to an empty cell. for (COff off = ret_off + COff(1); off < m_iMaxPosOff; ++off) { - //int pos = incPos(m_iStartPos, off); - const CPos pos = m_iStartPos + off; + const CPos pos = incPos(m_iStartPos, off); + //const CPos pos = m_iStartPos + off; if (m_entries[pos].status != EntryState_Empty) { - //*pw_end = CSeqNo::incseq(m_iStartSeqNo, off - 1); + //*pw_end = CSeqNo::incseq(m_iStartSeqNo, (off - 1) VALUE); *pw_end = (m_iStartSeqNo + (off - COff(1)) VALUE) VALUE; return ret_seq VALUE; } @@ -1645,7 +1635,7 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) // Fallback - this should be impossible, so issue a log. LOGC(rbuflog.Error, log << "IPE: empty cell pos=" << frompos VALUE << " %" //<< CSeqNo::incseq(m_iStartSeqNo, ret_off) - << (m_iStartSeqNo + ret_off) VALUE + << (m_iStartSeqNo + ret_off VALUE) VALUE << " not followed by any valid cell"); // Return this in the last resort - this could only be a situation when diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 18450947d..ff659a45c 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -18,7 +18,7 @@ #include "utilities.h" #define USE_WRAPPERS 1 -#define USE_OPERATORS 1 +#define USE_OPERATORS 0 namespace srt { @@ -33,7 +33,7 @@ struct CPos int isize() const {return *psize;} - explicit CPos(explicit_t ps, explicit_t val): value(val), psize(ps) {} + explicit CPos(const size_t* ps, int val): value(val), psize(ps) {} int val() const { return value; } explicit operator int() const {return value;} @@ -642,25 +642,64 @@ class CRcvBuffer const CUnit* peek(int32_t seqno); private: - /* - inline int incPos(int pos, int inc = 1) const { return (pos + inc) % m_szSize; } - inline int decPos(int pos) const { return (pos - 1) >= 0 ? (pos - 1) : int(m_szSize - 1); } - inline int offPos(int pos1, int pos2) const { return (pos2 >= pos1) ? (pos2 - pos1) : int(m_szSize + pos2 - pos1); } + //* + inline CPos incPos(CPos pos, COff inc = COff(1)) const { return CPos(&m_szSize, (pos VALUE + inc VALUE) % m_szSize); } + inline CPos decPos(CPos pos) const { return (pos VALUE - 1) >= 0 ? CPos(&m_szSize, pos VALUE - 1) : CPos(&m_szSize, m_szSize - 1); } + inline COff offPos(CPos pos1, CPos pos2) const + { + int diff = pos2 VALUE - pos1 VALUE; + if (diff >= 0) + { + return COff(diff); + } + return COff(m_szSize + diff); + } + + inline COff posToOff(CPos pos) const { return offPos(m_iStartPos, pos); } /// @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 int cmpPos(int pos2, int pos1) const + 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. - const int off1 = pos1 >= m_iStartPos ? pos1 - m_iStartPos : pos1 + (int)m_szSize - m_iStartPos; - const int off2 = pos2 >= m_iStartPos ? pos2 - m_iStartPos : pos2 + (int)m_szSize - m_iStartPos; - return off2 - off1; + 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.psize, (iStartPos VALUE + iMaxPosOff VALUE) % int(iSize)); + //const CPos iLastPos = iStartPos + iMaxPosOff; + const bool isOverrun = iLastPos VALUE < iStartPos VALUE; + + if (isOverrun) + return iFirstNonreadPos VALUE > iStartPos VALUE || iFirstNonreadPos VALUE <= iLastPos VALUE; + + return iFirstNonreadPos VALUE > iStartPos VALUE && iFirstNonreadPos VALUE <= iLastPos VALUE; + } + + bool isInUsedRange(CPos iFirstNonreadPos) + { + if (iFirstNonreadPos == m_iStartPos) + return true; + + // DECODE the iFirstNonreadPos + int diff = iFirstNonreadPos VALUE - m_iStartPos VALUE; + if (diff < 0) + diff += m_szSize; + + return diff <= m_iMaxPosOff VALUE; } - */ // NOTE: Assumes that pUnit != NULL CPacket& packetAt(CPos pos) { return m_entries[pos].pUnit->m_Packet; } From a2b1b44b35c883ac176b5273856d1224bc5af089 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Mon, 17 Jun 2024 11:40:52 +0200 Subject: [PATCH 56/62] First working version with end/drop as offset --- CMakeLists.txt | 2 +- srtcore/buffer_rcv.cpp | 611 +++++++++++++++++++++------------------ srtcore/buffer_rcv.h | 123 +++++--- test/test_buffer_rcv.cpp | 71 +++++ testing/testmedia.cpp | 24 +- 5 files changed, 493 insertions(+), 338 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c5994d3b7..70e1f50a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1528,7 +1528,7 @@ if (ENABLE_UNITTESTS AND ENABLE_CXX11) #set_tests_properties(test-srt PROPERTIES RUN_SERIAL TRUE) else() set_tests_properties(${tests_srt} PROPERTIES RUN_SERIAL TRUE) - gtest_discover_tests(test-srt) + #gtest_discover_tests(test-srt) endif() enable_testing() diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 539c51f9e..1562d0264 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -104,14 +104,14 @@ CRcvBuffer::CRcvBuffer(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool b , m_szSize(size) // TODO: maybe just use m_entries.size() , m_pUnitQueue(unitqueue) , m_iStartSeqNo(initSeqNo) // NOTE: SRT_SEQNO_NONE is allowed here. - , m_iStartPos(&m_szSize, 0) - , m_iEndPos(&m_szSize, 0) - , m_iDropPos(&m_szSize, 0) - , m_iFirstNonreadPos(&m_szSize, 0) + , m_iStartPos(0) + , m_iEndOff(0) + , m_iDropOff(0) + , m_iFirstNonreadPos(0) , m_iMaxPosOff(0) , m_iNotch(0) , m_numNonOrderPackets(0) - , m_iFirstNonOrderMsgPos(&m_szSize, CPos_TRAP.val()) + , m_iFirstNonOrderMsgPos(CPos_TRAP) , m_bPeerRexmitFlag(true) , m_bMessageAPI(bMessageAPI) , m_iBytesCount(0) @@ -138,8 +138,8 @@ void CRcvBuffer::debugShowState(const char* source SRT_ATR_UNUSED) { HLOGC(brlog.Debug, log << "RCV-BUF-STATE(" << source << ") start=" << m_iStartPos VALUE - << " end=" << m_iEndPos VALUE - << " drop=" << m_iDropPos VALUE + << " end=+" << m_iEndOff VALUE + << " drop=+" << m_iDropOff VALUE << " max-off=+" << m_iMaxPosOff VALUE << " seq[start]=%" << m_iStartSeqNo VALUE); } @@ -211,7 +211,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, newpktpos, extended_end); + time_point earlier_time = updatePosInfo(unit, prev_max_off, offset, extended_end); InsertInfo ireport (InsertInfo::INSERTED); ireport.first_time = earlier_time; @@ -237,102 +237,103 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) void CRcvBuffer::getAvailInfo(CRcvBuffer::InsertInfo& w_if) { - CPos fallback_pos = CPos_TRAP; - if (!m_tsbpd.isEnabled()) - { - // In case when TSBPD is off, we take into account the message mode - // where messages may potentially span for multiple packets, therefore - // the only "next deliverable" is the first complete message that satisfies - // the order requirement. - // NOTE THAT this field can as well be -1 already. - fallback_pos = m_iFirstNonOrderMsgPos; - } - else if (m_iDropPos != m_iEndPos) - { - // With TSBPD regard the drop position (regardless if - // TLPKTDROP is currently on or off), if "exists", that - // is, m_iDropPos != m_iEndPos. - fallback_pos = m_iDropPos; - } - - // This finds the first possible available packet, which is - // preferably at cell 0, but if not available, try also with - // given fallback position (unless it's -1). - const CPacket* pkt = tryAvailPacketAt(fallback_pos, (w_if.avail_range)); - if (pkt) - { - w_if.first_seq = CSeqNo(pkt->getSeqNo()); - } -} - + // This finds the first possible available packet, which is + // preferably at cell 0, but if not available, try also with + // given fallback position, if it's set + if (m_entries[m_iStartPos].status == EntryState_Avail) + { + const CPacket* pkt = &packetAt(m_iStartPos); + SRT_ASSERT(pkt); + w_if.avail_range = m_iEndOff; + w_if.first_seq = CSeqNo(pkt->getSeqNo()); + return; + } -const CPacket* CRcvBuffer::tryAvailPacketAt(CPos pos, COff& w_span) -{ - if (m_entries[m_iStartPos].status == EntryState_Avail) - { - pos = m_iStartPos; - w_span = offPos(m_iStartPos, m_iEndPos); - //w_span = m_iEndPos - m_iStartPos; - } + // If not the first position, probe the skipped positions: + // - for live mode, check the DROP position + // (for potential after-drop reading) + // - for message mode, check the non-order message position + // (for potential out-of-oder message delivery) - if (pos == CPos_TRAP) - { - w_span = COff(0); - return NULL; - } + const CPacket* pkt = NULL; + if (m_tsbpd.isEnabled()) + { + // With TSBPD you can rely on drop position, if set + if (m_iDropOff) + { + pkt = &packetAt(incPos(m_iStartPos, m_iDropOff)); + SRT_ASSERT(pkt); + } + } + else + { + // Message-mode: try non-order read position. + if (m_iFirstNonOrderMsgPos != CPos_TRAP) + { + pkt = &packetAt(m_iFirstNonOrderMsgPos); + SRT_ASSERT(pkt); + } + } - SRT_ASSERT(m_entries[pos].pUnit != NULL); + if (!pkt) + { + // This is default, but set just in case + // The default seq is SRT_SEQNO_NONE. + w_if.avail_range = COff(0); + return; + } - // TODO: we know that at least 1 packet is available, but only - // with m_iEndPos we know where the true range is. This could also - // be implemented for message mode, but still this would employ - // a separate begin-end range declared for a complete out-of-order - // message. - w_span = COff(1); - return &packetAt(pos); + // TODO: we know that at least 1 packet is available, but only + // with m_iEndOff we know where the true range is. This could also + // be implemented for message mode, but still this would employ + // a separate begin-end range declared for a complete out-of-order + // message. + w_if.avail_range = COff(1); + w_if.first_seq = CSeqNo(pkt->getSeqNo()); } -CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff prev_max_off, const CPos newpktpos, const bool extended_end) + +// 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) { time_point earlier_time; - CPos prev_max_pos = incPos(m_iStartPos, prev_max_off); - //CPos prev_max_pos = m_iStartPos + prev_max_off; - // Update flags // Case [A] if (extended_end) { // THIS means that the buffer WAS CONTIGUOUS BEFORE. - if (m_iEndPos == prev_max_pos) + if (m_iEndOff == prev_max_off) { // THIS means that the new packet didn't CAUSE a gap if (m_iMaxPosOff == prev_max_off + 1) { - // This means that m_iEndPos now shifts by 1, - // and m_iDropPos must be shifted together with it, + // This means that m_iEndOff now shifts by 1, + // and m_iDropOff must be shifted together with it, // as there's no drop to point. - m_iEndPos = incPos(m_iStartPos, m_iMaxPosOff); - //m_iEndPos = m_iStartPos + m_iMaxPosOff; - m_iDropPos = m_iEndPos; + m_iEndOff = m_iMaxPosOff; + m_iDropOff = 0; } else { // Otherwise we have a drop-after-gap candidate // which is the currently inserted packet. - // Therefore m_iEndPos STAYS WHERE IT IS. - m_iDropPos = incPos(m_iStartPos, m_iMaxPosOff - 1); - //m_iDropPos = m_iStartPos + (m_iMaxPosOff - 1); + // Therefore m_iEndOff STAYS WHERE IT IS. + m_iDropOff = m_iMaxPosOff - 1; } } } // - // Since this place, every newpktpos is in the range - // between m_iEndPos (inclusive) and a position for m_iMaxPosOff. + // Since this place, every 'offset' is in the range + // between m_iEndOff (inclusive) and m_iMaxPosOff. // Here you can use prev_max_pos as the position represented // by m_iMaxPosOff, as if !extended_end, it was unchanged. - else if (newpktpos == m_iEndPos) + else if (offset == m_iEndOff) { // Case [D]: inserted a packet at the first gap following the // contiguous region. This makes a potential to extend the @@ -342,22 +343,14 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff p // new earliest packet now. In any other situation under this // condition there's some contiguous packet range preceding // this position. - if (m_iEndPos == m_iStartPos) + if (m_iEndOff == 0) { earlier_time = getPktTsbPdTime(unit->m_Packet.getMsgTimeStamp()); } - updateGapInfo(prev_max_pos); + updateGapInfo(); } - // XXX Not sure if that's the best performant comparison - // What is meant here is that newpktpos is between - // m_iEndPos and m_iDropPos, though we know it's after m_iEndPos. - // CONSIDER: make m_iDropPos rather m_iDropOff, this will make - // this comparison a simple subtraction. Note that offset will - // have to be updated on every shift of m_iStartPos. - - //else if (cmpPos(newpktpos, m_iDropPos) < 0) - else if (newpktpos.cmp(m_iDropPos, m_iStartPos) < 0) + else if (offset < m_iDropOff) { // Case [C]: the newly inserted packet precedes the // previous earliest delivery position after drop, @@ -370,10 +363,10 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff p // // We know it because if the position has filled a gap following // a valid packet, this preceding valid packet would be pointed - // by m_iDropPos, or it would point to some earlier packet in a + // by m_iDropOff, or it would point to some earlier packet in a // contiguous series of valid packets following a gap, hence // the above condition wouldn't be satisfied. - m_iDropPos = newpktpos; + m_iDropOff = offset; // If there's an inserted packet BEFORE drop-pos (which makes it // a new drop-pos), while the very first packet is absent (the @@ -382,7 +375,7 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff p // position, but still following some earlier contiguous range // of valid packets - so it's earlier than previous drop, but // not earlier than the earliest packet. - if (m_iStartPos == m_iEndPos) + if (m_iEndOff == 0) { earlier_time = getPktTsbPdTime(unit->m_Packet.getMsgTimeStamp()); } @@ -392,37 +385,94 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff p return earlier_time; } -void CRcvBuffer::updateGapInfo(CPos prev_max_pos) +// This function is called when the m_iEndOff has been set to a new +// position and the m_iDropOff should be calculated since that position again. +void CRcvBuffer::updateGapInfo() { - CPos pos = m_iEndPos; + COff from = m_iEndOff, to = m_iMaxPosOff; + SRT_ASSERT(m_entries[incPos(m_iStartPos, m_iMaxPosOff)].status == EntryState_Empty); - // First, search for the next gap, max until m_iMaxPosOff. - for ( ; pos != prev_max_pos; ++pos /*pos = incPos(pos)*/) + CPos pos = incPos(m_iStartPos, from); + + if (m_entries[pos].status == EntryState_Avail) { - if (m_entries[pos].status == EntryState_Empty) + + CPos end_pos = incPos(m_iStartPos, m_iMaxPosOff); + + for (; pos != end_pos; pos = incPos(pos)) { - break; + if (m_entries[pos].status != EntryState_Avail) + break; } + + m_iEndOff = offPos(m_iStartPos, pos); } - if (pos == prev_max_pos) + + // XXX This should be this way, but there are still inconsistencies + // in the message code. + //SRT_ASSERT(m_entries[incPos(m_iStartPos, m_iEndOff)].status == EntryState_Empty); + SRT_ASSERT(m_entries[incPos(m_iStartPos, m_iEndOff)].status != EntryState_Avail); + + // XXX Controversy: m_iDropOff is only used in case when SRTO_TLPKTDROP + // is set. This option is not handled in message mode, only in live mode. + // Dropping by packet makes sense only in case of packetwise reading, + // which isn't the case of neither stream nor message mode. + if (!m_tsbpd.isEnabled()) { - // Reached the end and found no gaps. - m_iEndPos = prev_max_pos; - m_iDropPos = prev_max_pos; + m_iDropOff = 0; + return; } - else + + // Do not touch m_iDropOff if it's still beside the contiguous + // region. DO NOT SEARCH for m_iDropOff if m_iEndOff is max + // because this means that the whole buffer is contiguous. + // That would simply find nothing and only uselessly burden the + // performance by searching for a not present empty cell. + + // Also check if the current drop position is a readable packet. + // If not, start over. + CPos drop_pos = incPos(m_iStartPos, m_iDropOff); + + if (m_iDropOff < m_iEndOff || m_entries[drop_pos].status != EntryState_Avail) { - // Found a gap at pos - m_iEndPos = pos; - m_iDropPos = pos; // fallback, although SHOULD be impossible - // So, search for the first position to drop up to. - for ( ; pos != prev_max_pos; ++pos /*pos = incPos(pos)*/) + m_iDropOff = 0; + if (m_iEndOff < m_iMaxPosOff) { - if (m_entries[pos].status != EntryState_Empty) + int maxend = m_szSize - m_iStartPos VALUE; + int ifrom = m_iEndOff + 1; + int ito = m_iMaxPosOff VALUE; + + bool found = false; + for (int i = ifrom; i < std::min(maxend, ito); ++i) { - m_iDropPos = pos; - break; + if (m_entries[CPos(i)].status == EntryState_Avail) + { + m_iDropOff = i; + found = true; + break; + } } + + if (!found && ito > maxend) + { + int upto = ito - maxend; + for (int i = 0; i < upto; ++i) + { + if (m_entries[CPos(i)].status == EntryState_Avail) + { + m_iDropOff = i; + found = true; + break; + } + } + } + + // Must be found somewhere, worst case at the position + // of m_iMaxPosOff-1. If no finding loop caught it somehow, + // it will remain at 0. The case when you have empty packets + // in the busy range is only with message mode after reading + // packets out-of-order, but this doesn't use tsbpd mode. + SRT_ASSERT(m_iDropOff != 0); } } } @@ -444,9 +494,9 @@ int CRcvBuffer::dropUpTo(int32_t seqno) return 0; } - m_iMaxPosOff -= len; - if (m_iMaxPosOff < 0) - m_iMaxPosOff = 0; + m_iMaxPosOff = decOff(m_iMaxPosOff, len); + m_iEndOff = decOff(m_iEndOff, len); + m_iDropOff = decOff(m_iDropOff, len); const int iDropCnt = len VALUE; while (len VALUE > 0) @@ -454,8 +504,8 @@ int CRcvBuffer::dropUpTo(int32_t seqno) dropUnitInPos(m_iStartPos); m_entries[m_iStartPos].status = EntryState_Empty; SRT_ASSERT(m_entries[m_iStartPos].pUnit == NULL && m_entries[m_iStartPos].status == EntryState_Empty); - //m_iStartPos = incPos(m_iStartPos); - ++m_iStartPos; + m_iStartPos = incPos(m_iStartPos); + //++m_iStartPos; --len; } @@ -465,10 +515,7 @@ int CRcvBuffer::dropUpTo(int32_t seqno) // (This call MAY shift m_iStartSeqNo further.) releaseNextFillerEntries(); - // Start from here and search fort the next gap - m_iEndPos = m_iDropPos = m_iStartPos; - updateGapInfo(incPos(m_iStartPos, m_iMaxPosOff)); - //updateGapInfo(m_iStartPos + m_iMaxPosOff); + updateGapInfo(); // If the nonread position is now behind the starting position, set it to the starting position and update. // Preceding packets were likely missing, and the non read position can probably be moved further now. @@ -538,7 +585,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro if (bKeepExisting && bnd == PB_SOLO) { bDropByMsgNo = false; // Solo packet, don't search for the rest of the message. - LOGC(rbuflog.Debug, + HLOGC(rbuflog.Debug, log << "CRcvBuffer::dropMessage(): Skipped dropping an existing SOLO packet %" << packetAt(i).getSeqNo() << "."); continue; @@ -565,6 +612,14 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro //minDroppedOffset = i - m_iStartPos; } + if (end_off > m_iMaxPosOff) + { + HLOGC(rbuflog.Debug, log << "CRcvBuffer::dropMessage: requested to drop up to %" << seqnohi + << " with highest in the buffer %" << CSeqNo::incseq(m_iStartSeqNo VALUE, end_off) + << " - updating the busy region"); + m_iMaxPosOff = end_off; + } + if (bDropByMsgNo) { // If msgno is specified, potentially not the whole message was dropped using seqno range. @@ -574,7 +629,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro // Try to drop by the message number in case the message starts earlier than @a seqnolo. const CPos stop_pos = decPos(m_iStartPos); //const CPos stop_pos = m_iStartPos - COff(1); - for (CPos i = start_pos; i != stop_pos; --i) + for (CPos i = start_pos; i != stop_pos; i = decPos(i)) { // Can't drop if message number is not known. if (!m_entries[i].pUnit) // also dropped earlier. @@ -607,14 +662,23 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro IF_RCVBUF_DEBUG(scoped_log.ss << " iDropCnt " << iDropCnt); } + if (iDropCnt) + { + // We don't need the drop position, if we allow to drop messages by number + // and with that value we risk that drop was pointing to a dropped packet. + // Theoretically to make it consistent we need to shift the value to the + // next found packet, but we don't need this information if we use the message + // mode (because drop-by-packet is not supported in this mode) and this + // will burden the performance for nothing. + m_iDropOff = 0; + } + // Check if units before m_iFirstNonreadPos are dropped. const bool needUpdateNonreadPos = (minDroppedOffset != -1 && minDroppedOffset <= getRcvDataSize()); releaseNextFillerEntries(); - // XXX TEST AND FIX - // Start from the last updated start pos and search fort the next gap - m_iEndPos = m_iDropPos = m_iStartPos; - updateGapInfo(end_pos); + updateGapInfo(); + IF_HEAVY_LOGGING(debugShowState( ("dropmsg off %" + Sprint(seqnolo) + " #" + Sprint(msgno)).c_str())); @@ -636,7 +700,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro bool CRcvBuffer::getContiguousEnd(int32_t& w_seq) const { - if (m_iStartPos == m_iEndPos) + if (m_iEndOff == 0) { // Initial contiguous region empty (including empty buffer). HLOGC(rbuflog.Debug, log << "CONTIG: empty, give up base=%" << m_iStartSeqNo VALUE); @@ -644,18 +708,15 @@ bool CRcvBuffer::getContiguousEnd(int32_t& w_seq) const return m_iMaxPosOff > 0; } - COff end_off = offPos(m_iStartPos, m_iEndPos); - //COff end_off = m_iEndPos - m_iStartPos; - - //w_seq = CSeqNo::incseq(m_iStartSeqNo, end_off); - w_seq = (m_iStartSeqNo + end_off VALUE) VALUE; + w_seq = CSeqNo::incseq(m_iStartSeqNo VALUE, m_iEndOff VALUE); + //w_seq = (m_iStartSeqNo + m_iEndOff VALUE) VALUE; - HLOGC(rbuflog.Debug, log << "CONTIG: endD=" << end_off VALUE + HLOGC(rbuflog.Debug, log << "CONTIG: endD=" << m_iEndOff VALUE << " maxD=" << m_iMaxPosOff VALUE << " base=%" << m_iStartSeqNo VALUE << " end=%" << w_seq); - return (end_off < m_iMaxPosOff); + return (m_iEndOff < m_iMaxPosOff); } int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair* pw_seqrange) @@ -681,7 +742,17 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair= 0); m_iStartSeqNo = CSeqNo(pktseqno) + 1; + ++nskipped; } else { @@ -759,72 +814,39 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair= 0); + + m_iEndOff = decOff(m_iEndOff, len); + } countBytes(-pkts_read, -bytes_extracted); releaseNextFillerEntries(); + // This will update the end position + updateGapInfo(); + if (!isInUsedRange( m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; //updateNonreadPos(); } - // Now that we have m_iStartPos potentially shifted, reinitialize - // m_iEndPos and m_iDropPos. - - CPos pend_pos = incPos(m_iStartPos, m_iMaxPosOff); - //CPos pend_pos = m_iStartPos + m_iMaxPosOff; - - // First check: is anything in the beginning - if (m_entries[m_iStartPos].status == EntryState_Avail) - { - // If so, shift m_iEndPos up to the first nonexistent unit - // XXX Try to optimize search by splitting into two loops if necessary. - - m_iEndPos = incPos(m_iStartPos); - //m_iEndPos = m_iStartPos + COff(1); - while (m_entries[m_iEndPos].status == EntryState_Avail) - { - m_iEndPos = incPos(m_iEndPos); - //m_iEndPos = m_iEndPos + COff(1); - if (m_iEndPos == pend_pos) - break; - } - - // If we had first packet available, then there's also no drop pos. - m_iDropPos = m_iEndPos; - } - else - { - // If not, reset m_iEndPos and search for the first after-drop candidate. - m_iEndPos = m_iStartPos; - m_iDropPos = m_iEndPos; - - // The container could have become empty. - // Stay here if so. - if (m_iStartPos != pend_pos) - { - while (m_entries[m_iDropPos].status != EntryState_Avail) - { - m_iDropPos = incPos(m_iDropPos); - //m_iDropPos = m_iDropPos + COff(1); - if (m_iDropPos == pend_pos) - { - // Nothing found - set drop pos equal to end pos, - // which means there's no drop - m_iDropPos = m_iEndPos; - break; - } - } - } - } - - if (!m_tsbpd.isEnabled()) // We need updateFirstReadableNonOrder() here even if we are reading inorder, // incase readable inorder packets are all read out. @@ -923,6 +945,15 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) m_iStartPos = p; --m_iMaxPosOff; SRT_ASSERT(m_iMaxPosOff VALUE >= 0); + + --m_iEndOff; + if (m_iEndOff < 0) + m_iEndOff = 0; + + --m_iDropOff; + if (m_iDropOff < 0) + m_iDropOff = 0; + //m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); ++m_iStartSeqNo; } @@ -1043,10 +1074,11 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstValidPacketInfo() const pkt = &packetAt(m_iStartPos); } // If not, get the information from the drop - else if (m_iDropPos != m_iEndPos) + else if (m_iDropOff) { - SRT_ASSERT(m_entries[m_iDropPos].pUnit); - pkt = &packetAt(m_iDropPos); + CPos drop_pos = incPos(m_iStartPos, m_iDropOff); + SRT_ASSERT(m_entries[drop_pos].pUnit); + pkt = &packetAt(drop_pos); pi.seq_gap = true; // Available, but after a drop. } else @@ -1165,21 +1197,43 @@ bool CRcvBuffer::dropUnitInPos(CPos pos) return true; } -void CRcvBuffer::releaseNextFillerEntries() +int CRcvBuffer::releaseNextFillerEntries() { CPos pos = m_iStartPos; + int nskipped = 0; + while (m_entries[pos].status == EntryState_Read || m_entries[pos].status == EntryState_Drop) { + if (nskipped == m_iMaxPosOff) + { + // This should never happen. All the previously read- or drop-marked + // packets should be contained in the range up to m_iMaxPosOff. Do not + // let the buffer ride any further and report the problem. Still stay there. + LOGC(rbuflog.Error, log << "releaseNextFillerEntries: IPE: Read/Drop status outside the busy range!"); + break; + } + //m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); ++m_iStartSeqNo; releaseUnitInPos(pos); - //pos = incPos(pos); - ++pos; + pos = incPos(pos); + //++pos; m_iStartPos = pos; - --m_iMaxPosOff; - if (m_iMaxPosOff < 0) - m_iMaxPosOff = 0; + ++nskipped; } + + if (!nskipped) + { + return nskipped; + } + + m_iMaxPosOff -= nskipped; + m_iEndOff = decOff(m_iEndOff, nskipped); + + // Drop off will be updated after that call, if needed. + m_iDropOff = 0; + + return nskipped; } // TODO: Is this function complete? There are some comments left inside. @@ -1197,7 +1251,7 @@ void CRcvBuffer::updateNonreadPos() if (m_bMessageAPI && (packetAt(pos).getMsgBoundary() & PB_FIRST) == 0) break; - for (CPos i = pos; i != end_pos; ++i) // i = incPos(i)) + for (CPos i = pos; i != end_pos; i = incPos(i)) { if (!m_entries[i].pUnit || m_entries[pos].status != EntryState_Avail) { @@ -1226,7 +1280,7 @@ void CRcvBuffer::updateNonreadPos() CPos CRcvBuffer::findLastMessagePkt() { - for (CPos i = m_iStartPos; i != m_iFirstNonreadPos; ++i) //i = incPos(i)) + for (CPos i = m_iStartPos; i != m_iFirstNonreadPos; i = incPos(i)) { SRT_ASSERT(m_entries[i].pUnit); @@ -1333,7 +1387,7 @@ void CRcvBuffer::updateFirstReadableNonOrder() CPos posLast = CPos_TRAP; int msgNo = -1; - for (CPos pos = m_iStartPos; outOfOrderPktsRemain; ++pos) //pos = incPos(pos)) + for (CPos pos = m_iStartPos; outOfOrderPktsRemain; pos = incPos(pos)) { if (!m_entries[pos].pUnit) { @@ -1393,8 +1447,8 @@ CPos CRcvBuffer::scanNonOrderMessageRight(const CPos startPos, int msgNo) const CPos pos = startPos; do { - //pos = incPos(pos); - ++pos; + pos = incPos(pos); + //++pos; if (!m_entries[pos].pUnit) break; @@ -1424,8 +1478,8 @@ CPos CRcvBuffer::scanNonOrderMessageLeft(const CPos startPos, int msgNo) const CPos pos = startPos; do { - //pos = decPos(pos); - --pos; + pos = decPos(pos); + //--pos; if (!m_entries[pos].pUnit) return CPos_TRAP; @@ -1549,100 +1603,89 @@ void CRcvBuffer::updRcvAvgDataSize(const steady_clock::time_point& now) int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) { + // This means that there are no lost seqs at all, no matter + // from which position they would have to be checked. + if (m_iEndOff == m_iMaxPosOff) + return SRT_SEQNO_NONE; + //int offset = CSeqNo::seqoff(m_iStartSeqNo, fromseq); int offset_val = CSeqNo(fromseq) - m_iStartSeqNo; COff offset (offset_val); - // Check if it's still inside the buffer - if (offset_val < 0 || offset >= m_iMaxPosOff) + // Check if it's still inside the buffer. + // Skip the region from 0 to m_iEndOff because this + // region is by definition contiguous and contains no loss. + if (offset_val < m_iEndOff || offset >= m_iMaxPosOff) { HLOGC(rbuflog.Debug, log << "getFirstLossSeq: offset=" << offset VALUE << " for %" << fromseq << " (with max=" << m_iMaxPosOff VALUE << ") - NO LOSS FOUND"); return SRT_SEQNO_NONE; } - // Start position - CPos frompos = incPos(m_iStartPos, offset); - //CPos frompos = m_iStartPos + offset; - - // Ok; likely we should stand at the m_iEndPos position. - // If this given position is earlier than this, then - // m_iEnd stands on the first loss, unless it's equal - // to the position pointed by m_iMaxPosOff. - - CSeqNo ret_seq = CSeqNo(SRT_SEQNO_NONE); - COff ret_off = m_iMaxPosOff; - COff end_off = offPos(m_iStartPos, m_iEndPos); - //COff end_off = m_iEndPos - m_iStartPos; - if (offset < end_off) + // Check if this offset is equal to m_iEndOff. If it is, + // then you have the loss sequence exactly the one that + // was passed. Skip now, pw_end was not requested. + if (offset == m_iEndOff) { - // If m_iEndPos has such a value, then there are - // no loss packets at all. - if (end_off != m_iMaxPosOff) + if (pw_end) { - //ret_seq = CSeqNo::incseq(m_iStartSeqNo VALUE, end_off VALUE); - ret_seq = m_iStartSeqNo + end_off VALUE; - ret_off = end_off; + // If the offset is exactly at m_iEndOff, then + // m_iDropOff will mark the end of gap. + if (m_iDropOff) + *pw_end = CSeqNo::incseq(m_iStartSeqNo VALUE, m_iDropOff); + else + { + LOGC(rbuflog.Error, log << "getFirstLossSeq: IPE: drop-off=0 while seq-off == end-off != max-off"); + *pw_end = fromseq; + } } + return fromseq; } - else - { - // Could be strange, but just as the caller wishes: - // find the first loss since this point on - // You can't rely on m_iEndPos, you are beyond that now. - // So simply find the next hole. - // REUSE offset as a control variable - for (; offset < m_iMaxPosOff; ++offset) + int ret_seq = SRT_SEQNO_NONE; + int loss_off = 0; + // Now find the first empty position since here, + // up to m_iMaxPosOff. Checking against m_iDropOff + // makes no sense because if it is not 0, you'll + // find it earlier by checking packet presence. + for (int off = offset; off < m_iMaxPosOff; ++off) + { + CPos ipos ((m_iStartPos VALUE + off) % m_szSize); + if (m_entries[ipos].status == EntryState_Empty) { - const CPos pos = incPos(m_iStartPos, offset); - //const CPos pos = m_iStartPos + offset; - if (m_entries[pos].status == EntryState_Empty) - { - ret_off = offset; - //ret_seq = CSeqNo::incseq(m_iStartSeqNo, offset); - ret_seq = m_iStartSeqNo + offset VALUE; - break; - } + ret_seq = CSeqNo::incseq(m_iStartSeqNo VALUE, off); + loss_off = off; + break; } } - // If found no loss, just return this value and do not - // rewrite nor look for anything. - - // Also no need to search anything if only the beginning was - // being looked for. - if (ret_seq == CSeqNo(SRT_SEQNO_NONE) || !pw_end) - return ret_seq VALUE; - - // We want also the end range, so continue from where you - // stopped. + if (ret_seq == SRT_SEQNO_NONE) + { + // This is theoretically possible if we search from behind m_iEndOff, + // after m_iDropOff. This simply means that we are trying to search + // behind the last gap in the buffer. + return ret_seq; + } - // Start from ret_off + 1 because we know already that ret_off - // points to an empty cell. - for (COff off = ret_off + COff(1); off < m_iMaxPosOff; ++off) + // We get this position, so search for the end of gap + if (pw_end) { - const CPos pos = incPos(m_iStartPos, off); - //const CPos pos = m_iStartPos + off; - if (m_entries[pos].status != EntryState_Empty) + for (int off = loss_off+1; off < m_iMaxPosOff; ++off) { - //*pw_end = CSeqNo::incseq(m_iStartSeqNo, (off - 1) VALUE); - *pw_end = (m_iStartSeqNo + (off - COff(1)) VALUE) VALUE; - return ret_seq VALUE; + CPos ipos ((m_iStartPos VALUE + off) % m_szSize); + if (m_entries[ipos].status != EntryState_Empty) + { + *pw_end = CSeqNo::incseq(m_iStartSeqNo VALUE, off); + return ret_seq; + } } - } - // Fallback - this should be impossible, so issue a log. - LOGC(rbuflog.Error, log << "IPE: empty cell pos=" << frompos VALUE << " %" - //<< CSeqNo::incseq(m_iStartSeqNo, ret_off) - << (m_iStartSeqNo + ret_off VALUE) VALUE - << " not followed by any valid cell"); - - // Return this in the last resort - this could only be a situation when - // a packet has somehow disappeared, but it contains empty cells up to the - // end of buffer occupied range. This shouldn't be possible at all because - // there must be a valid packet at least at the last occupied cell. - return SRT_SEQNO_NONE; + // Should not be possible to not find an existing packet + // following the gap, otherwise there would be no gap. + LOGC(rbuflog.Error, log << "getFirstLossSeq: IPE: gap since %" << ret_seq << " not covered by existing packet"); + *pw_end = ret_seq; + } + return ret_seq; } diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index ff659a45c..230efa481 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -29,22 +29,39 @@ namespace srt struct CPos { int value; +#if USE_OPERATORS const size_t* psize; - int isize() const {return *psize;} +#endif + +#if USE_OPERATORS + explicit CPos(const size_t* ps SRT_ATR_UNUSED, int val) + : value(val) + , psize(ps) + {} + +#else + explicit CPos(int val): value(val) {} +#endif - explicit CPos(const size_t* ps, int val): value(val), psize(ps) {} int val() const { return value; } explicit operator int() const {return value;} - CPos(const CPos& src): value(src.value), psize(src.psize) {} + CPos(const CPos& src): value(src.value) +#if USE_OPERATORS + , psize(src.psize) +#endif + {} CPos& operator=(const CPos& src) { +#if USE_OPERATORS psize = src.psize; +#endif value = src.value; return *this; } +#if USE_OPERATORS int cmp(CPos other, CPos start) const { int pos2 = value; @@ -72,6 +89,7 @@ struct CPos value = 0; return *this; } +#endif bool operator == (CPos other) const { return value == other.value; } bool operator != (CPos other) const { return value != other.value; } @@ -116,6 +134,15 @@ struct COff bool operator > (int other) const { return value > other; } bool operator <= (int other) const { return value <= other; } bool operator >= (int other) const { return value >= other; } + + friend bool operator == (int value, COff that) { return value == that.value; } + friend bool operator != (int value, COff that) { return value != that.value; } + friend bool operator < (int value, COff that) { return value < that.value; } + friend bool operator > (int value, COff that) { return value > that.value; } + friend bool operator <= (int value, COff that) { return value <= that.value; } + friend bool operator >= (int value, COff that) { return value >= that.value; } + + operator bool() const { return value != 0; } }; #define VALUE .val() @@ -161,8 +188,7 @@ inline CSeqNo operator-(CSeqNo seq, COff off) #endif -const size_t fix_rollover = 16; -const CPos CPos_TRAP (&fix_rollover, -1); +const CPos CPos_TRAP (-1); #else #define VALUE @@ -174,22 +200,33 @@ const int CPos_TRAP = -1; // // 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 +// | | | | +// | 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_iDropPos -// | | -// | \___ m_iEndPos -// | -// \___ m_iStartPos: first packet position in the buffer -// -// m_pUnit[i]->status_: 0: free, 1: good, 2: read, 3: dropped (can be combined with read?) +// | | | | +// | | | \__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 @@ -199,30 +236,30 @@ const int CPos_TRAP = -1; // // // m_iStartPos: the first packet that should be read (might be empty) -// m_iEndPos: the end of contiguous range. Empty if m_iEndPos == m_iStartPos -// m_iDropPos: a packet available for retrieval after a drop. If == m_iEndPos, no such packet. +// 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_iEndPos = 0 -// m_iDropPos = 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_iEndPos shifted by 1 -// m_iDropPos = m_iEndPos +// m_iEndOff shifted by 1 +// m_iDropOff = 0 // // 2. Position: after a loss, newest. // // m_iStartPos unchanged. -// m_iEndPos unchanged. -// m_iDropPos: -// - if it was == m_iEndPos, set to this +// 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 @@ -417,8 +454,7 @@ class CRcvBuffer /// InsertInfo insert(CUnit* unit); - time_point updatePosInfo(const CUnit* unit, const COff prev_max_off, const CPos newpktpos, const bool extended_end); - const CPacket* tryAvailPacketAt(CPos pos, COff& w_span); + time_point updatePosInfo(const CUnit* unit, const COff prev_max_off, const COff offset, const bool extended_end); void getAvailInfo(InsertInfo& w_if); /// Update the values of `m_iEndPos` and `m_iDropPos` in @@ -436,12 +472,7 @@ class CRcvBuffer /// update the m_iEndPos and m_iDropPos fields, or set them /// both to the end of range if there are no loss gaps. /// - /// The @a prev_max_pos parameter is passed here because it is already - /// calculated in insert(), otherwise it would have to be calculated here again. - /// - /// @param prev_max_pos buffer position represented by `m_iMaxPosOff` - /// - void updateGapInfo(CPos prev_max_pos); + void updateGapInfo(); /// Drop packets in the receiver buffer from the current position up to the seqno (excluding seqno). /// @param [in] seqno drop units up to this sequence number @@ -643,9 +674,9 @@ class CRcvBuffer private: //* - inline CPos incPos(CPos pos, COff inc = COff(1)) const { return CPos(&m_szSize, (pos VALUE + inc VALUE) % m_szSize); } - inline CPos decPos(CPos pos) const { return (pos VALUE - 1) >= 0 ? CPos(&m_szSize, pos VALUE - 1) : CPos(&m_szSize, m_szSize - 1); } - inline COff offPos(CPos pos1, CPos pos2) const + CPos incPos(CPos pos, COff inc = COff(1)) const { return CPos((pos VALUE + inc VALUE) % m_szSize); } + CPos decPos(CPos pos) const { return (pos VALUE - 1) >= 0 ? CPos(pos VALUE - 1) : CPos(m_szSize - 1); } + COff offPos(CPos pos1, CPos pos2) const { int diff = pos2 VALUE - pos1 VALUE; if (diff >= 0) @@ -655,7 +686,15 @@ class CRcvBuffer return COff(m_szSize + diff); } - inline COff posToOff(CPos pos) const { return offPos(m_iStartPos, pos); } + COff posToOff(CPos pos) const { return offPos(m_iStartPos, pos); } + + static COff decOff(COff val, int shift) + { + int ival = val VALUE - 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. @@ -678,7 +717,7 @@ class CRcvBuffer if (iFirstNonreadPos == iStartPos) return true; - const CPos iLastPos = CPos(iStartPos.psize, (iStartPos VALUE + iMaxPosOff VALUE) % int(iSize)); + const CPos iLastPos = CPos((iStartPos VALUE + iMaxPosOff VALUE) % int(iSize)); //const CPos iLastPos = iStartPos + iMaxPosOff; const bool isOverrun = iLastPos VALUE < iStartPos VALUE; @@ -717,7 +756,9 @@ class CRcvBuffer /// Release entries following the current buffer position if they were already /// read out of order (EntryState_Read) or dropped (EntryState_Drop). - void releaseNextFillerEntries(); + /// + /// @return the range for which the start pos has been shifted + int releaseNextFillerEntries(); bool hasReadableInorderPkts() const { return (m_iFirstNonreadPos != m_iStartPos); } @@ -790,8 +831,8 @@ class CRcvBuffer CSeqNo m_iStartSeqNo; CPos m_iStartPos; // the head position for I/O (inclusive) - CPos m_iEndPos; // past-the-end of the contiguous region since m_iStartPos - CPos m_iDropPos; // points past m_iEndPos to the first deliverable after a gap, or == m_iEndPos if no such packet + 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) COff m_iMaxPosOff; // the furthest data position int m_iNotch; // index of the first byte to read in the first ready-to-read packet (used in file/stream mode) diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp index 10be243a9..9436fa77d 100644 --- a/test/test_buffer_rcv.cpp +++ b/test/test_buffer_rcv.cpp @@ -646,6 +646,77 @@ TEST_F(CRcvBufferReadMsg, MsgOutOfOrderDrop) EXPECT_EQ(m_unit_queue->size(), m_unit_queue->capacity()); } +TEST_F(CRcvBufferReadMsg, MsgOrderScraps) +{ + // Ok, in this test we're filling the message this way: + // 1. We have an empty packet in the first cell. + // 2. This is followed by a 5-packet message that is valid. + // 3. This is followed by empty, valid, empty, valid, valid packet, + // where all valid packets belong to the same message. + // 4. After that there should be 3-packet valid messsage. + // 5. We deploy drop request to that second scrapped message. + // 6. We read one message. Should be the first message. + // 7. We read one message. Should be the last message. + + auto& rcv_buffer = *m_rcv_buffer.get(); + + // 1, 2 + addMessage(5,// packets + 2, // msgno + m_init_seqno + 1, + true); + + // LAYOUT: 10 11 12 13 + // [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [A] [B] [C] [D] [E] [F] + // * (2 2 2 2 2) * 3 * 3 3) (4 4 4) + + // 3 + addPacket( + m_init_seqno + 7, + 3, + false, false, // subsequent + true); + + addPacket( + m_init_seqno + 9, + 3, + false, false, // subsequent + true); + + addPacket( + m_init_seqno + 10, + 3, + false, true, // last + true); + + // 4 + addMessage(3, // packets + 4, // msgno + m_init_seqno + 11, + true); + + // 5 + EXPECT_GT(rcv_buffer.dropMessage(m_init_seqno+8, m_init_seqno+8, 3, CRcvBuffer::KEEP_EXISTING), 0); + + // 6 + array buff; + SRT_MSGCTRL mc; + pair seqrange; + EXPECT_TRUE(rcv_buffer.readMessage(buff.data(), buff.size(), (&mc), (&seqrange)) == m_payload_sz); + EXPECT_EQ(mc.msgno, 2); + EXPECT_EQ(seqrange, make_pair(m_init_seqno+1, m_init_seqno+5)); + + CRcvBuffer::InsertInfo ii; + rcv_buffer.getAvailInfo((ii)); + EXPECT_EQ(ii.first_seq.val(), m_init_seqno+11); + + // 7 + EXPECT_TRUE(rcv_buffer.readMessage(buff.data(), buff.size(), (&mc), (&seqrange)) == m_payload_sz); + EXPECT_EQ(mc.msgno, 4); + EXPECT_EQ(seqrange, make_pair(m_init_seqno+11, m_init_seqno+13)); + +} + // One message (4 packets) is added to the buffer after a message with "in order" flag. // Read in order TEST_F(CRcvBufferReadMsg, MsgOutOfOrderAfterInOrder) diff --git a/testing/testmedia.cpp b/testing/testmedia.cpp index 2d6635288..1bf63ed64 100755 --- a/testing/testmedia.cpp +++ b/testing/testmedia.cpp @@ -1059,8 +1059,8 @@ void SrtCommon::OpenGroupClient() if (!extras.empty()) { Verb() << "?" << extras[0] << VerbNoEOL; - for (size_t i = 1; i < extras.size(); ++i) - Verb() << "&" << extras[i] << VerbNoEOL; + for (size_t x = 1; x < extras.size(); ++x) + Verb() << "&" << extras[x] << VerbNoEOL; } Verb(); @@ -1130,15 +1130,15 @@ void SrtCommon::OpenGroupClient() // spread the setting on all sockets. ConfigurePost(m_sock); - for (size_t i = 0; i < targets.size(); ++i) + for (size_t x = 0; x < targets.size(); ++x) { // As m_group_nodes is simply transformed into 'targets', // one index can be used to index them all. You don't // have to check if they have equal addresses because they // are equal by definition. - if (targets[i].id != -1 && targets[i].errorcode == SRT_SUCCESS) + if (targets[x].id != -1 && targets[x].errorcode == SRT_SUCCESS) { - m_group_nodes[i].socket = targets[i].id; + m_group_nodes[x].socket = targets[x].id; } } @@ -1159,12 +1159,12 @@ void SrtCommon::OpenGroupClient() } m_group_data.resize(size); - for (size_t i = 0; i < m_group_nodes.size(); ++i) + for (size_t x = 0; x < m_group_nodes.size(); ++x) { - SRTSOCKET insock = m_group_nodes[i].socket; + SRTSOCKET insock = m_group_nodes[x].socket; if (insock == -1) { - Verb() << "TARGET '" << sockaddr_any(targets[i].peeraddr).str() << "' connection failed."; + Verb() << "TARGET '" << sockaddr_any(targets[x].peeraddr).str() << "' connection failed."; continue; } @@ -1194,11 +1194,11 @@ void SrtCommon::OpenGroupClient() NULL, NULL) != -1) { Verb() << "[C]" << VerbNoEOL; - for (int i = 0; i < len1; ++i) - Verb() << " " << ready_conn[i] << VerbNoEOL; + for (int x = 0; x < len1; ++x) + Verb() << " " << ready_conn[x] << VerbNoEOL; Verb() << "[E]" << VerbNoEOL; - for (int i = 0; i < len2; ++i) - Verb() << " " << ready_err[i] << VerbNoEOL; + for (int x = 0; x < len2; ++x) + Verb() << " " << ready_err[x] << VerbNoEOL; Verb() << ""; From ff16e6871036a25622b47e92fe7867284cd7596c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Mon, 17 Jun 2024 13:52:01 +0200 Subject: [PATCH 57/62] Blocked development support types and fixed the test --- srtcore/buffer_rcv.cpp | 32 ++++++++++++++++---------------- srtcore/buffer_rcv.h | 17 ++++++++++------- srtcore/common.h | 8 ++++++++ test/test_buffer_rcv.cpp | 4 ++-- 4 files changed, 36 insertions(+), 25 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 1562d0264..494dc3763 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -141,7 +141,7 @@ void CRcvBuffer::debugShowState(const char* source SRT_ATR_UNUSED) << " end=+" << m_iEndOff VALUE << " drop=+" << m_iDropOff VALUE << " max-off=+" << m_iMaxPosOff VALUE - << " seq[start]=%" << m_iStartSeqNo VALUE); + << " seq[start]=%" << m_iStartSeqNo.val()); } CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) @@ -389,7 +389,7 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff p // position and the m_iDropOff should be calculated since that position again. void CRcvBuffer::updateGapInfo() { - COff from = m_iEndOff, to = m_iMaxPosOff; + COff from = m_iEndOff; //, to = m_iMaxPosOff; SRT_ASSERT(m_entries[incPos(m_iStartPos, m_iMaxPosOff)].status == EntryState_Empty); CPos pos = incPos(m_iStartPos, from); @@ -536,8 +536,8 @@ int CRcvBuffer::dropAll() if (empty()) return 0; - //const int end_seqno = CSeqNo::incseq(m_iStartSeqNo, m_iMaxPosOff); - const int end_seqno = (m_iStartSeqNo + m_iMaxPosOff VALUE) VALUE; + const int32_t end_seqno = CSeqNo::incseq(m_iStartSeqNo.val(), m_iMaxPosOff); + //const int end_seqno = (m_iStartSeqNo + m_iMaxPosOff VALUE) VALUE; return dropUpTo(end_seqno); } @@ -556,7 +556,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro if (offset_b < 0) { LOGC(rbuflog.Debug, log << "CRcvBuffer.dropMessage(): nothing to drop. Requested [" << seqnolo << "; " - << seqnohi << "]. Buffer start " << m_iStartSeqNo VALUE << "."); + << seqnohi << "]. Buffer start " << m_iStartSeqNo.val() << "."); return 0; } @@ -615,7 +615,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro if (end_off > m_iMaxPosOff) { HLOGC(rbuflog.Debug, log << "CRcvBuffer::dropMessage: requested to drop up to %" << seqnohi - << " with highest in the buffer %" << CSeqNo::incseq(m_iStartSeqNo VALUE, end_off) + << " with highest in the buffer %" << CSeqNo::incseq(m_iStartSeqNo.val(), end_off) << " - updating the busy region"); m_iMaxPosOff = end_off; } @@ -703,17 +703,17 @@ bool CRcvBuffer::getContiguousEnd(int32_t& w_seq) const if (m_iEndOff == 0) { // Initial contiguous region empty (including empty buffer). - HLOGC(rbuflog.Debug, log << "CONTIG: empty, give up base=%" << m_iStartSeqNo VALUE); - w_seq = m_iStartSeqNo VALUE; + HLOGC(rbuflog.Debug, log << "CONTIG: empty, give up base=%" << m_iStartSeqNo.val()); + w_seq = m_iStartSeqNo.val(); return m_iMaxPosOff > 0; } - w_seq = CSeqNo::incseq(m_iStartSeqNo VALUE, m_iEndOff VALUE); + w_seq = CSeqNo::incseq(m_iStartSeqNo.val(), m_iEndOff VALUE); //w_seq = (m_iStartSeqNo + m_iEndOff VALUE) VALUE; HLOGC(rbuflog.Debug, log << "CONTIG: endD=" << m_iEndOff VALUE << " maxD=" << m_iMaxPosOff VALUE - << " base=%" << m_iStartSeqNo VALUE + << " base=%" << m_iStartSeqNo.val() << " end=%" << w_seq); return (m_iEndOff < m_iMaxPosOff); @@ -1096,10 +1096,10 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstValidPacketInfo() const std::pair CRcvBuffer::getAvailablePacketsRange() const { const COff nonread_off = offPos(m_iStartPos, m_iFirstNonreadPos); - const int seqno_last = CSeqNo::incseq(m_iStartSeqNo VALUE, nonread_off VALUE); + const int seqno_last = CSeqNo::incseq(m_iStartSeqNo.val(), nonread_off VALUE); //const int nonread_off = (m_iFirstNonreadPos - m_iStartPos) VALUE; //const int seqno_last = (m_iStartSeqNo + nonread_off) VALUE; - return std::pair(m_iStartSeqNo VALUE, seqno_last); + return std::pair(m_iStartSeqNo.val(), seqno_last); } bool CRcvBuffer::isRcvDataReady(time_point time_now) const @@ -1539,7 +1539,7 @@ string CRcvBuffer::strFullnessState(int32_t iFirstUnackSeqNo, const time_point& { stringstream ss; - ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo VALUE + ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo.val() << " m_iStartPos=" << m_iStartPos VALUE << " m_iMaxPosOff=" << m_iMaxPosOff VALUE << ". "; ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << m_szSize << " pkts. "; @@ -1632,7 +1632,7 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) // If the offset is exactly at m_iEndOff, then // m_iDropOff will mark the end of gap. if (m_iDropOff) - *pw_end = CSeqNo::incseq(m_iStartSeqNo VALUE, m_iDropOff); + *pw_end = CSeqNo::incseq(m_iStartSeqNo.val(), m_iDropOff); else { LOGC(rbuflog.Error, log << "getFirstLossSeq: IPE: drop-off=0 while seq-off == end-off != max-off"); @@ -1653,7 +1653,7 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) CPos ipos ((m_iStartPos VALUE + off) % m_szSize); if (m_entries[ipos].status == EntryState_Empty) { - ret_seq = CSeqNo::incseq(m_iStartSeqNo VALUE, off); + ret_seq = CSeqNo::incseq(m_iStartSeqNo.val(), off); loss_off = off; break; } @@ -1675,7 +1675,7 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) CPos ipos ((m_iStartPos VALUE + off) % m_szSize); if (m_entries[ipos].status != EntryState_Empty) { - *pw_end = CSeqNo::incseq(m_iStartSeqNo VALUE, off); + *pw_end = CSeqNo::incseq(m_iStartSeqNo.val(), off); return ret_seq; } } diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 230efa481..c843abcec 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -17,7 +17,7 @@ #include "tsbpd_time.h" #include "utilities.h" -#define USE_WRAPPERS 1 +#define USE_WRAPPERS 0 #define USE_OPERATORS 0 namespace srt @@ -25,6 +25,9 @@ namespace srt // DEVELOPMENT TOOL - TO BE MOVED ELSEWHERE (like common.h) +// NOTE: This below series of definitions for CPos and COff +// are here for development support only, but they are not in +// use in the release code - there CPos and COff are aliases to int. #if USE_WRAPPERS struct CPos { @@ -545,11 +548,11 @@ class CRcvBuffer public: /// Get the starting position of the buffer as a packet sequence number. - int getStartSeqNo() const { return m_iStartSeqNo VALUE; } + int32_t getStartSeqNo() const { return m_iStartSeqNo.val(); } /// Sets the start seqno of the buffer. /// Must be used with caution and only when the buffer is empty. - void setStartSeqNo(int seqno) { m_iStartSeqNo = CSeqNo(seqno); } + void setStartSeqNo(int32_t seqno) { m_iStartSeqNo = CSeqNo(seqno); } /// Given the sequence number of the first unacknowledged packet /// tells the size of the buffer available for packets. @@ -561,16 +564,16 @@ class CRcvBuffer // Therefore if the first packet in the buffer is ahead of the iFirstUnackSeqNo // then it does not have acknowledged packets and its full capacity is available. // Otherwise subtract the number of acknowledged but not yet read packets from its capacity. - const CSeqNo iRBufSeqNo = m_iStartSeqNo; - //if (CSeqNo::seqcmp(iRBufSeqNo, iFirstUnackSeqNo) >= 0) // iRBufSeqNo >= iFirstUnackSeqNo - if (iRBufSeqNo >= CSeqNo(iFirstUnackSeqNo)) + const int32_t iRBufSeqNo = m_iStartSeqNo.val(); + if (CSeqNo::seqcmp(iRBufSeqNo, iFirstUnackSeqNo) >= 0) // iRBufSeqNo >= iFirstUnackSeqNo + //if (iRBufSeqNo >= CSeqNo(iFirstUnackSeqNo)) { // Full capacity is available. return capacity(); } // Note: CSeqNo::seqlen(n, n) returns 1. - return capacity() - CSeqNo::seqlen(iRBufSeqNo VALUE, iFirstUnackSeqNo) + 1; + return capacity() - CSeqNo::seqlen(iRBufSeqNo, iFirstUnackSeqNo) + 1; } /// @brief Checks if the buffer has packets available for reading regardless of the TSBPD. diff --git a/srtcore/common.h b/srtcore/common.h index 1c15dd01a..e0d7212cc 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -685,14 +685,20 @@ class CSeqNo inline static int32_t incseq(int32_t seq) {return (seq == m_iMaxSeqNo) ? 0 : seq + 1;} + CSeqNo inc() const { return CSeqNo(incseq(value)); } + inline static int32_t decseq(int32_t seq) {return (seq == 0) ? m_iMaxSeqNo : seq - 1;} + CSeqNo dec() const { return CSeqNo(decseq(value)); } + inline static int32_t incseq(int32_t seq, int32_t inc) {return (m_iMaxSeqNo - seq >= inc) ? seq + inc : seq - m_iMaxSeqNo + inc - 1;} // m_iMaxSeqNo >= inc + sec --- inc + sec <= m_iMaxSeqNo // if inc + sec > m_iMaxSeqNo then return seq + inc - (m_iMaxSeqNo+1) + CSeqNo inc(int32_t i) const { return CSeqNo(incseq(value, i)); } + inline static int32_t decseq(int32_t seq, int32_t dec) { // Check if seq - dec < 0, but before it would have happened @@ -705,6 +711,8 @@ class CSeqNo return seq - dec; } + CSeqNo dec(int32_t i) const { return CSeqNo(decseq(value, i)); } + static int32_t maxseq(int32_t seq1, int32_t seq2) { if (seqcmp(seq1, seq2) < 0) diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp index 9436fa77d..155dd7bdd 100644 --- a/test/test_buffer_rcv.cpp +++ b/test/test_buffer_rcv.cpp @@ -702,7 +702,7 @@ TEST_F(CRcvBufferReadMsg, MsgOrderScraps) array buff; SRT_MSGCTRL mc; pair seqrange; - EXPECT_TRUE(rcv_buffer.readMessage(buff.data(), buff.size(), (&mc), (&seqrange)) == m_payload_sz); + EXPECT_TRUE(rcv_buffer.readMessage(buff.data(), buff.size(), (&mc), (&seqrange)) == m_payload_sz*5); EXPECT_EQ(mc.msgno, 2); EXPECT_EQ(seqrange, make_pair(m_init_seqno+1, m_init_seqno+5)); @@ -711,7 +711,7 @@ TEST_F(CRcvBufferReadMsg, MsgOrderScraps) EXPECT_EQ(ii.first_seq.val(), m_init_seqno+11); // 7 - EXPECT_TRUE(rcv_buffer.readMessage(buff.data(), buff.size(), (&mc), (&seqrange)) == m_payload_sz); + EXPECT_TRUE(rcv_buffer.readMessage(buff.data(), buff.size(), (&mc), (&seqrange)) == m_payload_sz*3); EXPECT_EQ(mc.msgno, 4); EXPECT_EQ(seqrange, make_pair(m_init_seqno+11, m_init_seqno+13)); From fa70fdaf0ee32ee8ae42a9c48d4c8cbe2c2afc24 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Tue, 18 Jun 2024 12:55:09 +0200 Subject: [PATCH 58/62] Merged changes --- srtcore/buffer_rcv.cpp | 15 +++++++++++++++ srtcore/buffer_rcv.h | 12 ++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index a323360bb..d790345a6 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -435,6 +435,20 @@ void CRcvBuffer::updateGapInfo() m_iDropOff = 0; if (m_iEndOff < m_iMaxPosOff) { + CPos start = incPos(m_iStartPos, m_iEndOff + 1), + end = incPos(m_iStartPos, m_iEndOff); + + for (CPos i = start; i != end; i = incPos(i)) + { + if (m_entries[i].status == EntryState_Avail) + { + m_iDropOff = offPos(m_iStartPos, i); + break; + } + } + + /* OPTIMIZED, but buggy. + int maxend = m_szSize - m_iStartPos VALUE; int ifrom = m_iEndOff + 1; int ito = m_iMaxPosOff VALUE; @@ -463,6 +477,7 @@ void CRcvBuffer::updateGapInfo() } } } + */ // Must be found somewhere, worst case at the position // of m_iMaxPosOff-1. If no finding loop caught it somehow, diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 7ad000b69..325245194 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -208,14 +208,14 @@ const int CPos_TRAP = -1; // 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 | | +// | 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[] // +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ From 1bf93ff4afc1843c8716d159ebe1b4f8a0cbe7a4 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Tue, 18 Jun 2024 15:33:09 +0200 Subject: [PATCH 59/62] Cleanup of the commented-out code --- srtcore/buffer_rcv.cpp | 56 +++++------------------------------------- 1 file changed, 6 insertions(+), 50 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index d790345a6..9e19884b7 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -148,7 +148,6 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) { SRT_ASSERT(unit != NULL); const int32_t seqno = unit->m_Packet.getSeqNo(); - //const int offset = CSeqNo::seqoff(m_iStartSeqNo, seqno); const COff offset = COff(CSeqNo(seqno) - m_iStartSeqNo); IF_RCVBUF_DEBUG(ScopedLog scoped_log); @@ -387,7 +386,7 @@ CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff p // position and the m_iDropOff should be calculated since that position again. void CRcvBuffer::updateGapInfo() { - COff from = m_iEndOff; //, to = m_iMaxPosOff; + COff from = m_iEndOff; SRT_ASSERT(m_entries[incPos(m_iStartPos, m_iMaxPosOff)].status == EntryState_Empty); CPos pos = incPos(m_iStartPos, from); @@ -407,7 +406,7 @@ void CRcvBuffer::updateGapInfo() // XXX This should be this way, but there are still inconsistencies // in the message code. - //SRT_ASSERT(m_entries[incPos(m_iStartPos, m_iEndOff)].status == EntryState_Empty); + //USE: SRT_ASSERT(m_entries[incPos(m_iStartPos, m_iEndOff)].status == EntryState_Empty); SRT_ASSERT(m_entries[incPos(m_iStartPos, m_iEndOff)].status != EntryState_Avail); // XXX Controversy: m_iDropOff is only used in case when SRTO_TLPKTDROP @@ -447,38 +446,6 @@ void CRcvBuffer::updateGapInfo() } } - /* OPTIMIZED, but buggy. - - int maxend = m_szSize - m_iStartPos VALUE; - int ifrom = m_iEndOff + 1; - int ito = m_iMaxPosOff VALUE; - - bool found = false; - for (int i = ifrom; i < std::min(maxend, ito); ++i) - { - if (m_entries[CPos(i)].status == EntryState_Avail) - { - m_iDropOff = i; - found = true; - break; - } - } - - if (!found && ito > maxend) - { - int upto = ito - maxend; - for (int i = 0; i < upto; ++i) - { - if (m_entries[CPos(i)].status == EntryState_Avail) - { - m_iDropOff = i; - found = true; - break; - } - } - } - */ - // Must be found somewhere, worst case at the position // of m_iMaxPosOff-1. If no finding loop caught it somehow, // it will remain at 0. The case when you have empty packets @@ -566,8 +533,6 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro << m_iStartSeqNo); // Drop by packet seqno range to also wipe those packets that do not exist in the buffer. - //const int offset_a = CSeqNo::seqoff(m_iStartSeqNo, seqnolo); - //const int offset_b = CSeqNo::seqoff(m_iStartSeqNo, seqnohi); const int offset_a = CSeqNo(seqnolo) - m_iStartSeqNo; const int offset_b = CSeqNo(seqnohi) - m_iStartSeqNo; if (offset_b < 0) @@ -624,7 +589,6 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro m_entries[i].status = EntryState_Drop; if (minDroppedOffset == -1) minDroppedOffset = offPos(m_iStartPos, i); - //minDroppedOffset = i - m_iStartPos; } if (end_off > m_iMaxPosOff) @@ -812,7 +776,6 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair= 2, then probably there is a long gap, and buffer needs to be reset. - SRT_ASSERT((m_iStartPos VALUE + offset VALUE) / m_szSize < 2); + SRT_ASSERT((m_iStartPos + offset) / m_szSize < 2); - //const CPos newpktpos = m_iStartPos + offset; const CPos newpktpos = incPos(m_iStartPos, offset); const COff prev_max_off = m_iMaxPosOff; bool extended_end = false; @@ -193,7 +192,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 VALUE >= 0 && newpktpos VALUE < int(m_szSize)); + SRT_ASSERT(newpktpos >= 0 && newpktpos < int(m_szSize)); if (m_entries[newpktpos].status != EntryState_Empty) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -1"); @@ -466,7 +465,6 @@ std::pair CRcvBuffer::dropUpTo(int32_t seqno) IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::dropUpTo: seqno " << seqno << " m_iStartSeqNo " << m_iStartSeqNo); COff len = COff(CSeqNo(seqno) - m_iStartSeqNo); - //int len = CSeqNo::seqoff(m_iStartSeqNo, seqno); if (len <= 0) { IF_RCVBUF_DEBUG(scoped_log.ss << ". Nothing to drop."); @@ -479,7 +477,7 @@ std::pair CRcvBuffer::dropUpTo(int32_t seqno) int iNumDropped = 0; // Number of dropped packets that were missing. int iNumDiscarded = 0; // The number of dropped packets that existed in the buffer. - while (len VALUE > 0) + while (len > 0) { // Note! Dropping a EntryState_Read must not be counted as a drop because it was read. // Note! Dropping a EntryState_Drop must not be counted as a drop because it was already dropped and counted earlier. @@ -685,10 +683,10 @@ bool CRcvBuffer::getContiguousEnd(int32_t& w_seq) const return m_iMaxPosOff > 0; } - w_seq = CSeqNo::incseq(m_iStartSeqNo.val(), m_iEndOff VALUE); + w_seq = CSeqNo::incseq(m_iStartSeqNo.val(), m_iEndOff); - HLOGC(rbuflog.Debug, log << "CONTIG: endD=" << m_iEndOff VALUE - << " maxD=" << m_iMaxPosOff VALUE + HLOGC(rbuflog.Debug, log << "CONTIG: endD=" << m_iEndOff + << " maxD=" << m_iMaxPosOff << " base=%" << m_iStartSeqNo.val() << " end=%" << w_seq); @@ -917,7 +915,7 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) m_iStartPos = p; --m_iMaxPosOff; - SRT_ASSERT(m_iMaxPosOff VALUE >= 0); + SRT_ASSERT(m_iMaxPosOff >= 0); m_iEndOff = decOff(m_iEndOff, 1); m_iDropOff = decOff(m_iDropOff, 1); @@ -943,8 +941,8 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) if (iBytesRead == 0) { - LOGC(rbuflog.Error, log << "readBufferTo: 0 bytes read. m_iStartPos=" << m_iStartPos VALUE - << ", m_iFirstNonreadPos=" << m_iFirstNonreadPos VALUE); + LOGC(rbuflog.Error, log << "readBufferTo: 0 bytes read. m_iStartPos=" << m_iStartPos + << ", m_iFirstNonreadPos=" << m_iFirstNonreadPos); } IF_HEAVY_LOGGING(debugShowState("readbuf")); @@ -968,7 +966,7 @@ bool CRcvBuffer::hasAvailablePackets() const int CRcvBuffer::getRcvDataSize() const { - return offPos(m_iStartPos, m_iFirstNonreadPos) VALUE; + return offPos(m_iStartPos, m_iFirstNonreadPos); } int CRcvBuffer::getTimespan_ms() const @@ -1057,7 +1055,7 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstValidPacketInfo() const std::pair CRcvBuffer::getAvailablePacketsRange() const { const COff nonread_off = offPos(m_iStartPos, m_iFirstNonreadPos); - const CSeqNo seqno_last = m_iStartSeqNo + nonread_off VALUE; + const CSeqNo seqno_last = m_iStartSeqNo + nonread_off; return std::pair(m_iStartSeqNo.val(), seqno_last.val()); } @@ -1488,7 +1486,7 @@ string CRcvBuffer::strFullnessState(int32_t iFirstUnackSeqNo, const time_point& stringstream ss; ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo.val() - << " m_iStartPos=" << m_iStartPos VALUE << " m_iMaxPosOff=" << m_iMaxPosOff VALUE << ". "; + << " m_iStartPos=" << m_iStartPos << " m_iMaxPosOff=" << m_iMaxPosOff << ". "; ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << m_szSize << " pkts. "; @@ -1555,17 +1553,15 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) if (m_iEndOff == m_iMaxPosOff) return SRT_SEQNO_NONE; - //int offset = CSeqNo::seqoff(m_iStartSeqNo, fromseq); - int offset_val = CSeqNo(fromseq) - m_iStartSeqNo; - COff offset (offset_val); + COff offset = COff(CSeqNo(fromseq) - m_iStartSeqNo); // Check if it's still inside the buffer. // Skip the region from 0 to m_iEndOff because this // region is by definition contiguous and contains no loss. - if (offset_val < m_iEndOff || offset >= m_iMaxPosOff) + if (offset < m_iEndOff || offset >= m_iMaxPosOff) { - HLOGC(rbuflog.Debug, log << "getFirstLossSeq: offset=" << offset VALUE << " for %" << fromseq - << " (with max=" << m_iMaxPosOff VALUE << ") - NO LOSS FOUND"); + HLOGC(rbuflog.Debug, log << "getFirstLossSeq: offset=" << offset << " for %" << fromseq + << " (with max=" << m_iMaxPosOff << ") - NO LOSS FOUND"); return SRT_SEQNO_NONE; } @@ -1597,7 +1593,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 VALUE + off) % m_szSize); + CPos ipos ((m_iStartPos + off) % m_szSize); if (m_entries[ipos].status == EntryState_Empty) { ret_seq = CSeqNo::incseq(m_iStartSeqNo.val(), off); @@ -1619,7 +1615,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 VALUE + off) % m_szSize); + CPos ipos ((m_iStartPos + off) % m_szSize); 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 325245194..ec5f2c256 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -148,8 +148,6 @@ struct COff operator bool() const { return value != 0; } }; -#define VALUE .val() - #if USE_OPERATORS inline CPos operator+(const CPos& pos, COff off) @@ -194,7 +192,6 @@ inline CSeqNo operator-(CSeqNo seq, COff off) const CPos CPos_TRAP (-1); #else -#define VALUE typedef int CPos; typedef int COff; const int CPos_TRAP = -1; @@ -679,11 +676,11 @@ class CRcvBuffer private: //* - CPos incPos(CPos pos, COff inc = COff(1)) const { return CPos((pos VALUE + inc VALUE) % m_szSize); } - CPos decPos(CPos pos) const { return (pos VALUE - 1) >= 0 ? CPos(pos VALUE - 1) : CPos(m_szSize - 1); } + 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 VALUE - pos1 VALUE; + int diff = pos2 - pos1; if (diff >= 0) { return COff(diff); @@ -695,7 +692,7 @@ class CRcvBuffer static COff decOff(COff val, int shift) { - int ival = val VALUE - shift; + int ival = val - shift; if (ival < 0) return COff(0); return COff(ival); @@ -722,14 +719,13 @@ class CRcvBuffer if (iFirstNonreadPos == iStartPos) return true; - const CPos iLastPos = CPos((iStartPos VALUE + iMaxPosOff VALUE) % int(iSize)); - //const CPos iLastPos = iStartPos + iMaxPosOff; - const bool isOverrun = iLastPos VALUE < iStartPos VALUE; + const CPos iLastPos = CPos((iStartPos + iMaxPosOff) % int(iSize)); + const bool isOverrun = iLastPos < iStartPos; if (isOverrun) - return iFirstNonreadPos VALUE > iStartPos VALUE || iFirstNonreadPos VALUE <= iLastPos VALUE; + return iFirstNonreadPos > iStartPos || iFirstNonreadPos <= iLastPos; - return iFirstNonreadPos VALUE > iStartPos VALUE && iFirstNonreadPos VALUE <= iLastPos VALUE; + return iFirstNonreadPos > iStartPos && iFirstNonreadPos <= iLastPos; } bool isInUsedRange(CPos iFirstNonreadPos) @@ -738,11 +734,11 @@ class CRcvBuffer return true; // DECODE the iFirstNonreadPos - int diff = iFirstNonreadPos VALUE - m_iStartPos VALUE; + int diff = iFirstNonreadPos - m_iStartPos; if (diff < 0) diff += m_szSize; - return diff <= m_iMaxPosOff VALUE; + return diff <= m_iMaxPosOff; } // NOTE: Assumes that pUnit != NULL From cb00ccead13e7d5f884ff229a6c637cadc0464db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Tue, 3 Sep 2024 17:57:10 +0200 Subject: [PATCH 61/62] Added option to disable test discovery (off by default) --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b135fc72..215559326 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,6 +162,7 @@ option(ENABLE_PKTINFO "Enable using IP_PKTINFO to allow the listener extracting option(ENABLE_RELATIVE_LIBPATH "Should application contain relative library paths, like ../lib" OFF) option(ENABLE_GETNAMEINFO "In-logs sockaddr-to-string should do rev-dns" OFF) option(ENABLE_UNITTESTS "Enable unit tests" OFF) +option(DISABLE_UNITTESTS_DISCOVERY "Do not discover unit tests when enabled" OFF) option(ENABLE_ENCRYPTION "Enable encryption in SRT" ON) option(ENABLE_AEAD_API_PREVIEW "Enable AEAD API preview in SRT" Off) option(ENABLE_MAXREXMITBW "Enable SRTO_MAXREXMITBW (v1.6.0 API preview)" Off) @@ -1527,7 +1528,9 @@ if (ENABLE_UNITTESTS AND ENABLE_CXX11) #set_tests_properties(test-srt PROPERTIES RUN_SERIAL TRUE) else() set_tests_properties(${tests_srt} PROPERTIES RUN_SERIAL TRUE) - #gtest_discover_tests(test-srt) + if (NOT DISABLE_UNITTESTS_DISCOVERY) + gtest_discover_tests(test-srt) + endif() endif() enable_testing() From 5bc71e9506423760adb653f0b686eaf1d2452bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Ma=C5=82ecki?= Date: Wed, 4 Sep 2024 10:28:04 +0200 Subject: [PATCH 62/62] BUGFIX: the buffer info extraction function wasn't mutex-protected --- srtcore/core.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 802521d8a..b0ab9376c 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -8031,7 +8031,10 @@ bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) } if (m_config.bTSBPD || !m_config.bMessageAPI) { - // The getFirstNonreadSeqNo() function retuens the sequence number of the first packet + // NOTE: it's not only about protecting the buffer itself, it's also protecting + // the section where the m_iRcvCurrSeqNo is updated. + ScopedLock buflock (m_RcvBufferLock); + // The getFirstNonreadSeqNo() function returns the sequence number of the first packet // that cannot be read. In cases when a message can consist of several data packets, // an existing packet of partially available message also cannot be read. // If TSBPD mode is enabled, a message must consist of a single data packet only.