diff --git a/apps/socketoptions.hpp b/apps/socketoptions.hpp index 1ffb2febb..a430af41b 100644 --- a/apps/socketoptions.hpp +++ b/apps/socketoptions.hpp @@ -251,6 +251,7 @@ const SocketOption srt_options [] { { "packetfilter", 0, SRTO_PACKETFILTER, SocketOption::PRE, SocketOption::STRING, nullptr }, { "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}, { "bindtodevice", 0, SRTO_BINDTODEVICE, SocketOption::PRE, SocketOption::STRING, nullptr}, { "retransmitalgo", 0, SRTO_RETRANSMITALGO, SocketOption::PRE, SocketOption::INT, nullptr }, { "cryptomode", 0, SRTO_CRYPTOMODE, SocketOption::PRE, SocketOption::INT, nullptr }, diff --git a/docs/API/API-functions.md b/docs/API/API-functions.md index 154b64363..1f6ee0a05 100644 --- a/docs/API/API-functions.md +++ b/docs/API/API-functions.md @@ -2138,9 +2138,10 @@ number of bytes retrieved will be at most the maximum payload of one MTU. The [`SRTO_PAYLOADSIZE`](API-socket-options.md#SRTO_PAYLOADSIZE) value configured by the sender is not negotiated, and not known to the receiver. The [`SRTO_PAYLOADSIZE`](API-socket-options.md#SRTO_PAYLOADSIZE) value set on the SRT receiver -is mainly used for heuristics. However, the receiver is prepared to receive -the whole MTU as configured with [`SRTO_MSS`](API-socket-options.md#SRTO_MSS). -In this mode, however, with default settings of [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) +is mainly used for heuristics and as the minimum size of the buffer in this +call. However, the receiver is prepared to receive the whole MTU as configured +with [`SRTO_MSS`](API-socket-options.md#SRTO_MSS). In this mode, however, with +default settings of [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) and [`SRTO_TLPKTDROP`](API-socket-options.md#SRTO_TLPKTDROP), the message will be received only when its time to play has come, and until then it will be kept in the receiver buffer. Also, when the time to play has come for a message that is next to @@ -2149,7 +2150,7 @@ the currently lost one, it will be delivered and the lost one dropped. | Returns | | |:----------------------------- |:--------------------------------------------------------- | | Size value \> 0 | Size of the data received, if successful. | -| 0 | If the connection has been closed | +| 0 | No message is ready for retrieval | | `SRT_ERROR` | (-1) when an error occurs | | | | diff --git a/docs/dev/utilities.md b/docs/dev/utilities.md index f2d8950a8..e522c783d 100644 --- a/docs/dev/utilities.md +++ b/docs/dev/utilities.md @@ -77,6 +77,30 @@ Example: cout << endl; ``` +2.a. `BIT` macro - for simplifying macro definitions +---------------------------------------------------- + +The `BIT` macro allows to define symbolic constants assigned to bits. + +Example: +``` +#define SRTGROUP_MASK BIT(30) +``` + +Considered were other methods to define it, like: + +* an inline function: requires `constexpr`, available in C++11 +* a user-defined literal, like `30_bit`, available in C++17 + +but this can be used as long as C++03-compatibility must be maintained. + +2.b. `IsSet`: test if a bit is set in a bitmask +----------------------------------------------- + +This function should be used for testing if a runtime value of the type +representing a bit set through a 32-bit integer contains a single bit set. + + 3. DynamicStruct: a simple array that can be only indexed with a dedicated type. -------------------------------------------------------------------------------- @@ -96,9 +120,14 @@ compile error. 4. FixedArray: a simple wrapper for a dynamically allocated array. ------------------------------------------------------------------ -Provides a wrapper of all basic operations, `operator[]` as well as basic +It's a wrapper for a dynamically-allocated array with constant size. The +wrapper provides of all basic operations, `operator[]` as well as basic container methods: `begin(), end(), data(), size()` to satisfy the concept -of the STL random-access container. +of the STL random-access container. Important properties: + +* The size is constant for the lifetime, but it can be runtime-defined. +* You can use your custom type for indexer values in `operator[]`. +* The `operator[]` method checks the index value. 5. HeapSet: a partially sorted container using the heap tree concept @@ -112,7 +141,7 @@ class HeapSet This container implements a concept of a partially sorted container which guarantees always the element at the head to be the earliest in the sorting -order, and allows elements to be added to the container with partial storing. +order, and allows elements to be added to the container with partial sorting. The element is added at the quickest findable position in the tree, while pulling the earliest element causes tree rebalancing. @@ -121,7 +150,7 @@ The types for the template instantiation are: - NodeType: The type of the value kept in the container (representation of the contained objects). This type must be a lightweight-value type, so prefer things like integers, pointers or iterators. There must also exist a trap -representation for this type. +representation for this type (a value of "no object"). - Access: a class that provides static methods according to the requirements @@ -165,7 +194,7 @@ NodeType is possible, just use different AccessType). HeapSet state attributes: - `none()` : returns the trap representation for NodeType (as provided by - the AccessType class), for convemience + the AccessType class), for convenience - `npos` : an internal static constant assigned from std::string::npos - `raw()` : returns the constant reference to the internal heap array - `empty(), size()` : same as for the internal array @@ -245,7 +274,7 @@ Differences: - inserts only a default value - returns the reference to the value in the map -- works for value types that are not copiable +- works for value types that are not copyable The reference is returned because to return the node you would have to search for it after using operator[]. @@ -287,9 +316,10 @@ string with surrounding `[]` and values separated by space. Used in logging. if the value is already there, in which case nothing is inserted. * `Tie`: similar to `std::tie` for C++03: binds two variables by exposing - they references so that this can be used in the assignment + their references so that this can be used in the assignment -* `All`: returns a pair of iterators extracted from `begin()` and `end()` +* `All`: returns a pair of iterators extracted from `begin()` and `end()`. + This can be used in conjunction with `Tie` by assigning to its result * `Size`: a version of std::size from C++11 - for a fixed array it returns the number of declared elements; for other types it's size() method result. @@ -300,10 +330,10 @@ string with surrounding `[]` and values separated by space. Used in logging. iterator concept is supported, though; for random-access containers you should do it manually with checking size() and distance() -* `FringeValues`: Takes all values from the container and marks in the - output map, how many values of that kind were found. The output map - will then contain only unique values as keys and the value is the - number of found occurrences of this very value +* `FringeValues`: Take the values from the container and counts how many + times each unique value occur by inserting the values into the map. + The values in the output map represent the number of times a particular + value occurs. 12. CallbackHolder @@ -321,7 +351,7 @@ the call regarding the opaque pointer. This utility is used in window.cpp where it is required to calculate the median value basing on the value in the very middle and filtered out values exceeding -its range of 1/8 and 8 times. Returned is a structure that shows the median and +its range by 1/8 and 8 times. Returned is a structure that shows the median and also the lower and upper value used for filtering. This calculation does more-less the following: @@ -357,11 +387,11 @@ number of elements taken into account, through a pair. * AccumulatePassFilterParallel This function sums up all values in the array (from p to end) and -simultaneously elements from `para`, stated it points to an array of the same -size. The first array is used as a driver for which elements to include and -which to skip, and this is done for both arrays at particular index position. -Returner is the sum of the elements passed from the first array and from the -`para` array, as well as the number of included elements. +simultaneously elements from `para`, assuming that it points to an array of +the same size. The first array is used as a driver for which elements to +include and which to skip, and this is done for both arrays at particular index +position. Returned is the sum of the elements passed from the first array and +from the `para` array, as well as the number of included elements. 14. DriftTracer @@ -377,7 +407,7 @@ up to maximum history is kept in the container. A special value is declared as taken as a legitimate difference to fix. The values of `drift()` and `overdrift()` can be read at any time, however if -you want to depend on the fact that they have been changed lately, you have to +you want to rely on the fact that they have been changed lately, you have to check the return value from update(). IMPORTANT: drift() can be called at any time, just remember that this value may diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 22992fa53..8f0186742 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -176,6 +176,17 @@ void CUDTSocket::setBrokenClosed() bool CUDTSocket::readReady() const { +#if SRT_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 (m_GroupOf) + return false; +#endif if (m_UDT.m_bConnected && m_UDT.isRcvBufferReady()) return true; @@ -306,7 +317,7 @@ void CUDTUnited::cleanupAllSockets() { CUDTSocket* s = i->second; -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING if (s->m_GroupOf) { s->removeFromGroup(false); @@ -329,7 +340,7 @@ void CUDTUnited::cleanupAllSockets() } m_Sockets.clear(); -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING for (groups_t::iterator j = m_Groups.begin(); j != m_Groups.end(); ++j) { delete j->second; @@ -902,6 +913,10 @@ int CUDTUnited::newConnection(const SRTSOCKET listener, // be removed from the accept queue at this time. should_submit_to_accept = g->groupPending_LOCKED(); + // Ok, whether handled in the background, or reported through accept, + // all group-member sockets should be managed. + ns->core().m_bManaged = true; + // Update the status in the group so that the next // operation can include the socket in the group operation. CUDTGroup::SocketData* gm = ns->m_GroupMemberData; @@ -914,14 +929,17 @@ int CUDTUnited::newConnection(const SRTSOCKET listener, gm->laststatus = SRTS_CONNECTED; g->setGroupConnected(); - - - // Add also per-direction subscription for the about-to-be-accepted socket. - // 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; + // 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. + + // Keep per-socket sender ready EID. int write_modes = SRT_EPOLL_OUT | SRT_EPOLL_ERR; - epoll_add_usock_INTERNAL(g->m_RcvEID, ns, &read_modes); epoll_add_usock_INTERNAL(g->m_SndEID, ns, &write_modes); // With app reader, do not set groupPacketArrival (block the @@ -1865,7 +1883,7 @@ SRTSOCKET CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, // Do it after setting all stored options, as some of them may // influence some group data. - groups::SocketData data = groups::prepareSocketData(ns); + groups::SocketData data = groups::prepareSocketData(ns, g.type()); if (targets[tii].token != -1) { // Reuse the token, if specified by the caller @@ -1958,7 +1976,6 @@ SRTSOCKET CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, // 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); - epoll_add_usock_INTERNAL(g.m_RcvEID, ns, &connect_modes); // 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 @@ -2085,7 +2102,6 @@ SRTSOCKET CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, f->sndstate = SRT_GST_BROKEN; f->rcvstate = SRT_GST_BROKEN; epoll_remove_socket_INTERNAL(g.m_SndEID, ns); - epoll_remove_socket_INTERNAL(g.m_RcvEID, ns); } else { @@ -2171,7 +2187,6 @@ SRTSOCKET CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, epoll_remove_socket_INTERNAL(eid, y->second); epoll_remove_socket_INTERNAL(g.m_SndEID, y->second); - epoll_remove_socket_INTERNAL(g.m_RcvEID, y->second); } } @@ -2211,7 +2226,6 @@ SRTSOCKET CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, epoll_remove_socket_INTERNAL(eid, s); epoll_remove_socket_INTERNAL(g.m_SndEID, s); - epoll_remove_socket_INTERNAL(g.m_RcvEID, s); continue; } @@ -2401,7 +2415,7 @@ void CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) CUDTSocket* s = i->second; if (s->m_GroupOf == g) { - HLOGC(smlog.Debug, log << "deleteGroup: IPE: existing @" << s->id() << " points to a dead group!"); + LOGC(smlog.Error, log << "deleteGroup: IPE: existing @" << s->id() << " points to a dead group!"); s->m_GroupOf = NULL; s->m_GroupMemberData = NULL; } @@ -2414,7 +2428,7 @@ void CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) CUDTSocket* s = i->second; if (s->m_GroupOf == g) { - HLOGC(smlog.Debug, log << "deleteGroup: IPE: closed @" << s->id() << " points to a dead group!"); + LOGC(smlog.Error, log << "deleteGroup: IPE: closed @" << s->id() << " points to a dead group!"); s->m_GroupOf = NULL; s->m_GroupMemberData = NULL; } @@ -2965,6 +2979,8 @@ int CUDTUnited::select(std::set* readfds, std::set* writef return count; } +// XXX This may crash when a member socket is added to selectEx. +// Consider revising to prevent a member socket from being used. int CUDTUnited::selectEx(const vector& fds, vector* readfds, vector* writefds, @@ -2991,7 +3007,7 @@ int CUDTUnited::selectEx(const vector& fds, { CUDTSocket* s = locateSocket(*i); - if ((!s) || s->core().m_bBroken || (s->m_Status == SRTS_CLOSED)) + if ((!s) || s->core().m_bBroken || (s->m_Status == SRTS_CLOSED) || s->m_GroupOf) { if (exceptfds) { @@ -3350,6 +3366,12 @@ void CUDTUnited::checkBrokenSockets() continue; } else + + // 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. { CUDT& u = s->core(); diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index d0092e9bf..340494fda 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -98,10 +98,9 @@ namespace { * m_iMaxPosOff: none? (modified on add and ack */ -CRcvBuffer::CRcvBuffer(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool bMessageAPI) +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) // NOTE: SRT_SEQNO_NONE is allowed here. , m_iStartPos(0) , m_iEndOff(0) @@ -128,7 +127,7 @@ CRcvBuffer::~CRcvBuffer() if (!it->pUnit) continue; - m_pUnitQueue->makeUnitFree(it->pUnit); + it->pUnit->m_pParentQueue->makeUnitFree(it->pUnit); it->pUnit = NULL; } } @@ -200,7 +199,8 @@ CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) } SRT_ASSERT(m_entries[newpktpos].pUnit == NULL); - m_pUnitQueue->makeUnitTaken(unit); + CUnitQueue* q = unit->m_pParentQueue; + q->makeUnitTaken(unit); m_entries[newpktpos].pUnit = unit; m_entries[newpktpos].status = EntryState_Avail; countBytes(1, (int)unit->m_Packet.getLength()); @@ -530,7 +530,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro << m_iStartSeqNo); if (msgno < 0) // Note that only SRT_MSGNO_CONTROL is allowed in the protocol. { - HLOGC(rbuflog.Error, log << "EPE: received UMSG_DROPREQ with msgflag field set to a negative value!"); + LOGC(rbuflog.Error, log << "EPE: received UMSG_DROPREQ with msgflag field set to a negative value!"); } // Drop by packet seqno range to also wipe those packets that do not exist in the buffer. @@ -538,7 +538,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro const int offset_b = SeqNo(seqnohi) - m_iStartSeqNo; if (offset_b < 0) { - LOGC(rbuflog.Debug, log << "CRcvBuffer.dropMessage(): nothing to drop. Requested [" << seqnolo << "; " + HLOGC(rbuflog.Debug, log << "CRcvBuffer.dropMessage(): nothing to drop. Requested [" << seqnolo << "; " << seqnohi << "]. Buffer start " << m_iStartSeqNo.val() << "."); return 0; } @@ -696,7 +696,7 @@ bool CRcvBuffer::getContiguousEnd(int32_t& w_seq) const return (m_iEndOff < m_iMaxPosOff); } -int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair* pw_seqrange) +int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL& w_msgctrl, pair* pw_seqrange) { const bool canReadInOrder = hasReadableInorderPkts(); if (!canReadInOrder && m_iFirstNonOrderMsgPos == CPos_TRAP) @@ -763,16 +763,15 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pairmsgno = packet.getMsgSeq(m_bPeerRexmitFlag); + w_msgctrl.msgno = packet.getMsgSeq(m_bPeerRexmitFlag); } - if (msgctrl && pbLast) + if (pbLast) { - msgctrl->srctime = count_microseconds(getPktTsbPdTime(packet.getMsgTimeStamp()).time_since_epoch()); + w_msgctrl.srctime = count_microseconds(getPktTsbPdTime(packet.getMsgTimeStamp()).time_since_epoch()); } - if (msgctrl) - msgctrl->pktseq = pktseqno; + w_msgctrl.pktseq = pktseqno; releaseUnitInPos(i); if (isReadingFromStart) @@ -1142,7 +1141,7 @@ void CRcvBuffer::releaseUnitInPos(CPos 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 CRcvBuffer::dropUnitInPos(CPos pos) @@ -1192,11 +1191,66 @@ int CRcvBuffer::releaseNextFillerEntries() } m_iMaxPosOff = m_iMaxPosOff - nskipped; - m_iEndOff = decOff(m_iEndOff, nskipped); + m_iEndOff = m_iMaxPosOff ? decOff(m_iEndOff, nskipped) : 0; - // Drop off will be updated after that call, if needed. + // Drop will be updated at the call to updateGapInfo() m_iDropOff = 0; + // If it happened that after removal of the dropped packets the + // VERY FIRST packet doesn't have PB_FIRST flag (e.g. it's a scrapped + // message), remove all packets belonging to that message. + if (m_iMaxPosOff + && m_entries[m_iStartPos].pUnit + && m_entries[m_iStartPos].status == EntryState_Avail + && (packetAt(m_iStartPos).getMsgBoundary() & PB_FIRST) == 0) + { + const int32_t msgno = packetAt(m_iStartPos).getMsgSeq(); + + const CPos end_pos = incPos(m_iStartPos, m_iMaxPosOff); + CPos next_pos = incPos(m_iStartPos, 1); + ++nskipped; + + m_iStartSeqNo = m_iStartSeqNo.inc(); + releaseUnitInPos(m_iStartPos); + m_iStartPos = next_pos; + + // Delete only contiguous packets. Empty cell may potentially + // be a packet of a different message, so keep it. + + // We believe that this will end after a few items and never reach end_pos + for (CPos i = next_pos; i != end_pos; i = next_pos) + { + // Keep that value for convenience. + next_pos = incPos(i); + + // Empty cell - keep it. + // NOTE: Dropped cells should have been removed already, + // and we state that a dropped cell cannot follow the valid cell. + if (!m_entries[i].pUnit) + break; + + const CPacket& pkt = packetAt(i); + + // Different message - keep it. + if (pkt.getMsgSeq() != msgno) + break; + + ++nskipped; + + m_iStartSeqNo = m_iStartSeqNo.inc(); + releaseUnitInPos(i); + m_iStartPos = next_pos; + --m_iMaxPosOff; + + // You might have reached also the last one, + // while having that message id. + if ((pkt.getMsgBoundary() & PB_LAST) != 0) + { + break; + } + } + } + return nskipped; } @@ -1211,11 +1265,17 @@ void CRcvBuffer::updateNonreadPos() CPos pos = m_iFirstNonreadPos; while (m_entries[pos].pUnit && m_entries[pos].status == EntryState_Avail) { + // Message API AND packet at [m_iFirstNonreadPos] is PB_SUBSEQUENT or PB_LAST. if (m_bMessageAPI && (packetAt(pos).getMsgBoundary() & PB_FIRST) == 0) break; + // Continue the OUTER loop, when + // - There is an AVAIL unit at the [m_iFirstNonreadPos] cell + // - [m_iFirstNonreadPos] has set PB_FIRST (also PB_SOLO) + for (CPos i = pos; i != end_pos; i = incPos(i)) { + // Reach the end_pos or the end of sequence of available packets if (!m_entries[i].pUnit || m_entries[pos].status != EntryState_Avail) { break; @@ -1640,5 +1700,41 @@ int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) return ret_seq; } +void CRcvBuffer::getUnitSeriesInfo(int32_t fromseq, size_t maxsize, std::vector& w_sources) +{ + const int offset = SeqNo(fromseq) - m_iStartSeqNo; + + // 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; + } + } +} + +std::string CRcvBuffer::Entry::debug() +{ + static const char* const state_names[4] = {"EMPTY", "AVAIL", "READ", "DROPPED"}; + using namespace hvu; + + const char* stat = "INVALID"; + + if (int(status) >= 0 && int(status) <= 3) + stat = state_names[status]; + + return fmtcat(stat, ": ", pUnit ? pUnit->m_Packet.Info() : string("(no packet)")); +} } // namespace srt diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index 8de0be29b..2e6e3bcab 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -406,7 +406,7 @@ class CRcvBuffer typedef sync::steady_clock::duration duration; public: - CRcvBuffer(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool bMessageAPI); + CRcvBuffer(int initSeqNo, size_t size, /*CUnitQueue* unitqueue, */ bool bMessageAPI); ~CRcvBuffer(); @@ -531,7 +531,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, std::pair* pw_seqrange = NULL); + int readMessage(char* data, size_t len, SRT_MSGCTRL& msgctrl, std::pair* pw_seqrange = NULL); /// Read acknowledged data into a user buffer. /// @param [in, out] dst pointer to the target user buffer. @@ -624,6 +624,7 @@ class CRcvBuffer 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 { @@ -814,7 +815,7 @@ class CRcvBuffer EntryState_Empty, //< No CUnit record. EntryState_Avail, //< Entry is available for reading. EntryState_Read, //< Entry has already been read (out of order). - EntryState_Drop //< Entry has been dropped. + EntryState_Drop //< Entry has been dropped. }; struct Entry { @@ -825,13 +826,20 @@ class CRcvBuffer CUnit* pUnit; EntryStatus status; + + // For debug purposes + std::string debug(); }; 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 + + //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 // ATOMIC because getStartSeqNo() may be called from other thread // than CUDT's receiver worker thread. Even if it's not a problem diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 05e5d3b25..d2387aa21 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -552,12 +552,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) @@ -568,6 +576,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 2c0964068..8bd1f5479 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -171,6 +171,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; @@ -242,12 +244,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/channel.cpp b/srtcore/channel.cpp index 4ae9c61df..b5b415dfe 100644 --- a/srtcore/channel.cpp +++ b/srtcore/channel.cpp @@ -931,7 +931,7 @@ int CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const CNetworkIn private: WSAEVENT e; }; -#if !defined(__MINGW32__) && defined(ENABLE_CXX11) +#if !defined(__MINGW32__) && defined(HAVE_CXX11) thread_local WSAEventRef lEvent; #else WSAEventRef lEvent; diff --git a/srtcore/common.h b/srtcore/common.h index 4f5717e7a..6ed6e008e 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -805,6 +805,12 @@ class SeqNoT: public CSeqNo return *this; } + SeqNoT operator++(int) + { + SeqNoT old = *this; + value = incseq(value); + return old; + } SRT_ATR_NODISCARD SeqNoT inc() const { return SeqNoT(incseq(value)); } SRT_ATR_NODISCARD SeqNoT dec() const { return SeqNoT(decseq(value)); } diff --git a/srtcore/core.cpp b/srtcore/core.cpp index a869f95f8..5fba6947b 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -61,11 +61,6 @@ modified by #include #endif -#if !HAVE_CXX11 -// for pthread_once -#include -#endif - // Again, just in case when some "smart guy" provided such a global macro #ifdef min #undef min @@ -79,6 +74,7 @@ modified by #include #include #include +#include "srt_attr_defs.h" #include "srt.h" #include "access_control.h" // Required for SRT_REJX_FALLBACK #include "queue.h" @@ -98,9 +94,12 @@ using namespace hvu; // ofmt static const char* const s_hs_side[] = { "DRAW", "INITIATOR", "RESPONDER" }; #endif +// XXX For testing: use common loss list for also broadcast groups. +#define BROADCAST_COMMON_SND_LOSS 1 namespace srt { + static inline char fmt_onoff(bool val) { return val ? '+' : '-'; } // Mark unused because it's only used in HLOGC SRT_ATR_UNUSED static inline const char* fmt_yesno(bool val) { return val ? "yes" : "no"; } @@ -417,6 +416,9 @@ void CUDT::construct() m_bBufferWasFull = false; m_bManaged = 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) + // Will be updated on first send #ifdef SRT_ENABLE_MAXREXMITBW m_zSndAveragePacketSize = 0; @@ -2370,7 +2372,7 @@ int CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t bytelen, uint32_t // 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 @@ -3339,6 +3341,13 @@ bool CUDT::interpretGroup(CUDTSocket* lsn, const int32_t groupdata[], size_t dat return false; } + if (m_bTsbPd) + { + HLOGC(cnlog.Debug, log << "interpretGroup: socket TSBPD=on, switching to GROUP TSBPD"); + m_bGroupTsbPd = true; + m_bTsbPd = false; + } + if (m_SrtHsSide == HSD_INITIATOR) { // Here we'll be only using a pre-existing group, so shared is enough. @@ -3395,10 +3404,10 @@ bool CUDT::interpretGroup(CUDTSocket* lsn, const int32_t groupdata[], size_t dat log << CONID() << "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. @@ -3557,7 +3566,7 @@ SRTSOCKET CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint32_t l if (was_empty) { - gp->syncWithSocket(s->core(), HSD_RESPONDER); + gp->syncWithFirstSocket(s->core(), HSD_RESPONDER); } } @@ -3579,7 +3588,7 @@ SRTSOCKET CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint32_t l return SRT_SOCKID_CONNREQ; } - 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; @@ -3588,9 +3597,9 @@ SRTSOCKET CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint32_t l return gp->id(); } +//[[using GroupKeeper(gp)]] void CUDT::synchronizeWithGroup(CUDTGroup* gp) { - ScopedLock gl (*gp->exp_groupLock()); if (gp->isClosing()) return; @@ -3602,7 +3611,13 @@ void 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 << CONID() << "synchronizeWithGroup: ST=" << FormatTime(m_stats.tsStartTime) << " -> " @@ -3619,53 +3634,19 @@ void CUDT::synchronizeWithGroup(CUDTGroup* gp) << " PST=" << FormatTime(m_tsRcvPeerStartTime)); } - 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))) - { - // We have at least one socket in the group, each socket should have - // the value of the timebase set exactly THE SAME. - - // In case when we have the following situation: + // These are the values that are normally set initially by setters. + int32_t snd_isn = m_iSndLastAck, rcv_isn = m_iRcvLastAck; + // NOTE: Here is theoretically also the buffer lock required, but this + // function is called from acceptAndRespond when no modifications in the + // buffer or reading from any thread are for the time being possible. - // - the existing link is before [LAST30] (so wrap period is off) - // - the new link gets the timestamp from [LAST30] range - // --> this will be recognized as entering the wrap period, next - // timebase will get added a segment to this value - // - // The only dangerous situations could be when one link gets - // timestamps from the [FOLLOWING30] and the other in [FIRST30], - // but between them there's a 30s distance, considered large enough - // time to not fill a network window. - enterCS(m_RecvLock); - // NOTE: Here is theoretically also the buffer lock required, but this - // function is called from acceptAndRespond when no modifications in the - // buffer or reading from any thread are for the time being possible. - m_pRcvBuffer->applyGroupTime(rcv_buffer_time_base, rcv_buffer_wrap_period, m_iTsbPdDelay_ms * 1000, rcv_buffer_udrift); - m_pRcvBuffer->setPeerRexmitFlag(m_bPeerRexmitFlag); - leaveCS(m_RecvLock); - - HLOGC(gmlog.Debug, log << "AFTER HS: Set Rcv TsbPd mode: delay=" - << (m_iTsbPdDelay_ms/1000) << "." << (m_iTsbPdDelay_ms%1000) - << "s GROUP TIME BASE: " << FormatTime(rcv_buffer_time_base) - << " (" << (rcv_buffer_wrap_period ? "" : "NOT") << " WRAP PERIOD)"); - } - else + first_time = false; { - HLOGC(gmlog.Debug, - log << CONID() << "AFTER HS: (GROUP, but " - << (m_bTsbPd ? "FIRST SOCKET is initialized normally)" : "no TSBPD set)")); - updateSrtRcvSettings(); + ScopedLock gl (*gp->exp_groupLock()); + first_time = gp->applyGroupSequences(m_SocketID, (snd_isn), (rcv_isn)); } - // This function currently does nothing, just left for consistency - // with updateAfterSrtHandshake(). - updateSrtSndSettings(); - - // 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))) + if (!first_time) { HLOGC(gmlog.Debug, log << CONID() << "synchronizeWithGroup: DERIVED ISN: RCV=%" << m_iRcvLastAck << " -> %" << rcv_isn @@ -4387,9 +4368,9 @@ EConnectStatus 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) || !prepareBuffers(NULL)) + // The CryptoControl must be created before interpreting and creating HSv5 + // extensions because the it will be used there. + if (!createCrypter(m_SrtHsSide)) { // m_RejectReason already handled HLOGC(cnlog.Debug, @@ -4422,6 +4403,13 @@ EConnectStatus 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. @@ -4488,6 +4476,13 @@ EConnectStatus CUDT::processRendezvous( m_ConnReq.m_iReqType = URQFailure(m_RejectReason); return CONN_REJECT; } + + 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 (tosend_ext_type) @@ -4985,11 +4980,12 @@ EConnectStatus CUDT::postConnect(const CPacket* pResponse, bool rendezvous, CUDT // 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); ok = ok && interpretSrtHandshake(NULL, m_ConnRes, *pResponse, 0, 0); ok = ok && prepareBuffers(eout); @@ -5602,6 +5598,12 @@ void * 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; + } #if SRT_ENABLE_BONDING // Make the TSBPD thread a "client" of the group, // which will ensure that the group will not be physically @@ -5618,9 +5620,6 @@ void * CUDT::tsbpd(void* param) { steady_clock::time_point tsNextDelivery; // Next packet delivery time bool rxready = false; -#if SRT_ENABLE_BONDING - bool shall_update_group = false; -#endif INCREMENT_THREAD_ITERATIONS(); @@ -5659,9 +5658,6 @@ void * CUDT::tsbpd(void* param) { // XXX TSA: Requires lock on m_RcvBufferLock (locked already by enterCS) const int iDropCnt SRT_ATR_UNUSED = self->rcvDropTooLateUpTo(info.seqno); -#if SRT_ENABLE_BONDING - shall_update_group = true; -#endif #if HVU_ENABLE_LOGGING const int64_t timediff_us = count_microseconds(tnow - info.tsbpd_time); @@ -5708,7 +5704,7 @@ void * 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 SRT_ENABLE_BONDING +/* WAS ENABLED ON: #if ENABLE_OLD_BONDING // If this is NULL, it means: // - the socket never was a group member // - the socket was a group member, but: @@ -5751,13 +5747,13 @@ void * CUDT::tsbpd(void* param) gkeeper.group->updateLatestRcv(self->m_parent); } } +// #endif */ // After re-acquisition of the m_RecvLock, re-check the closing flag if (self->m_bClosing) { break; } -#endif CGlobEvent::triggerEvent(); tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } @@ -5813,6 +5809,7 @@ void * CUDT::tsbpd(void* param) return NULL; } +// This is to be called from tsbpd(). int CUDT::rcvDropTooLateUpTo(int seqno, DropReason reason) { // Make sure that it would not drop over m_iRcvCurrSeqNo, which may break senders. @@ -5848,6 +5845,8 @@ void 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)); + sync::ScopedLock rb(m_RcvBufferLock); if (m_pRcvBuffer) { @@ -5864,38 +5863,7 @@ void CUDT::setInitialRcvSeq(int32_t isn) } } -bool CUDT::prepareConnectionObjects(const CHandShake& hs SRT_ATR_UNUSED, HandshakeSide hsd, 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_CryptoControl.initialized()) - { - HLOGC(rslog.Debug, log << CONID() << "prepareConnectionObjects: (lazy) already created."); - return true; - } - - // 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) - { - hsd = HSD_RESPONDER; // In HSv5, listener is always RESPONDER and caller always INITIATOR. - } - - if (!createCrypter(hsd)) // Make sure CC is created (lazy) - { - if (eout) - *eout = CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); - m_RejectReason = SRT_REJ_RESOURCE; - return false; - } - - return true; -} - +// Called from tsbpd(). int CUDT::getAuthTagSize() const { if (m_CryptoControl.getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) @@ -5906,7 +5874,10 @@ int CUDT::getAuthTagSize() const bool CUDT::prepareBuffers(CUDTException* eout) { - if (m_pSndBuffer) + // 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_pSndLossList) { HLOGC(rslog.Debug, log << CONID() << "prepareBuffers: (lazy) already created."); return true; @@ -5918,6 +5889,13 @@ bool CUDT::prepareBuffers(CUDTException* eout) // packet that fits the transmission for the overall connection. For any mixed 4-6 // connection it should be the less size, that is, for IPv6 +#if SRT_ENABLE_BONDING + // Keep the per-socket receiver buffer and receiver loss list empty. + // Reception will be redirected to the group directly. + const bool isgroup = m_parent->m_GroupOf; +#else + const bool isgroup = false; +#endif // CryptoControl has to be initialized and in case of RESPONDER the KM REQ must be processed (interpretSrtHandshake(..)) for the crypto mode to be deduced. const int authtag = getAuthTagSize(); @@ -5939,9 +5917,12 @@ bool CUDT::prepareBuffers(CUDTException* eout) << " authtag=" << authtag); m_pSndBuffer = new CSndBuffer(m_TransferIPVersion, 32, snd_payload_size, authtag); - SRT_ASSERT(m_iPeerISN != -1); - m_pRcvBuffer = new CRcvBuffer(m_iPeerISN, m_config.iRcvBufSize, m_pMuxer->getBufferQueue(), m_config.bMessageAPI); - // After introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice a space. + if (!isgroup) + { + SRT_ASSERT(m_iISN != SRT_SEQNO_NONE); + m_pRcvBuffer = new CRcvBuffer(m_iISN, m_config.iRcvBufSize, 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); m_pRcvLossList = new CRcvLossList(m_config.iFlightFlagSize); } @@ -6040,16 +6021,16 @@ void CUDT::acceptAndRespond(CUDTSocket* lsn, const sockaddr_any& peer, const CPa } - // Prepare all structures - if (!prepareConnectionObjects(w_hs, HSD_DRAW, 0)) + HandshakeSide hsd = w_hs.v5orHigher() || !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)) { - HLOGC(cnlog.Debug, - log << CONID() << "acceptAndRespond: prepareConnectionObjects 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. + HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: createCrypter failed - responding with REJECT."); w_hs.m_iReqType = URQFailure(m_RejectReason); throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } @@ -6400,7 +6381,7 @@ void CUDT::considerLegacySrtHandshake(const steady_clock::time_point &timebase) if (m_iSndHsRetryCnt <= 0) { - HLOGC(cnlog.Debug, log << CONID() << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt); + //HLOGC(cnlog.Debug, log << CONID() << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt); return; } @@ -6572,6 +6553,11 @@ bool CUDT::closeEntity(int reason) ATR_NOEXCEPT HLOGC(smlog.Debug, log << CONID() << "CLOSING STATE (closing=true). Acquiring connection lock"); + // 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 + // not necessary. ScopedLock connectguard(m_ConnectionLock); // Signal the sender and recver if they are waiting for data. @@ -7087,17 +7073,35 @@ int 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. - if (m_parent->m_GroupOf - && w_mctrl.pktseq != SRT_SEQNO_NONE - && m_iSndNextSeqNo != SRT_SEQNO_NONE) + // 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 (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; + 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); + } } } #endif @@ -7113,7 +7117,7 @@ int 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)); time_point start_time = m_stats.tsStartTime; if (w_mctrl.srctime && w_mctrl.srctime < count_microseconds(start_time.time_since_epoch())) @@ -7217,11 +7221,30 @@ int CUDT::recvmsg2(char* data, int len, SRT_MSGCTRL& w_mctrl) // [[using locked(m_RcvBufferLock)]] size_t CUDT::getAvailRcvBufferSizeNoLock() const { + +// This function is to be used instrumentally for +// cases under the socket's buffer lock. NOT TO BE USED +// for new bonding. +#if SRT_ENABLE_BONDING + SRT_ASSERT(m_parent->m_GroupOf == NULL); +#endif return m_pRcvBuffer->getAvailSize(m_iRcvLastAck); } bool CUDT::isRcvBufferReady() const { +#if SRT_ENABLE_BONDING + // 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); return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); } @@ -7257,6 +7280,12 @@ int CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_excep if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_MESSAGE, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0); +#if SRT_ENABLE_BONDING + // 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); @@ -7278,7 +7307,7 @@ int CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_excep HLOGC(arlog.Debug, log << CONID() << "receiveMessage: 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) + ? m_pRcvBuffer->readMessage((data), len, (w_mctrl)) : 0; leaveCS(m_RcvBufferLock); @@ -7317,7 +7346,7 @@ int CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_excep HLOGC(arlog.Debug, log << CONID() << "receiveMessage: 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) + ? m_pRcvBuffer->readMessage((data), len, (w_mctrl)) : 0; leaveCS(m_RcvBufferLock); HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (NON-BLOCKING) result=" << res); @@ -7437,7 +7466,7 @@ int CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_excep */ enterCS(m_RcvBufferLock); - res = m_pRcvBuffer->readMessage((data), len, &w_mctrl); + res = m_pRcvBuffer->readMessage((data), len, (w_mctrl)); leaveCS(m_RcvBufferLock); HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (BLOCKING) result=" << res); @@ -8293,6 +8322,18 @@ void CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rparam, bool 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 SRT_ENABLE_BONDING + CUDTUnited::GroupKeeper gk(uglobal(), m_parent); + CUDTGroup* g = m_parent->m_GroupOf; + if (g) + { + return g->getFirstNoncontSequence((w_seq), (w_log_reason)); + } +#endif + + SRT_ASSERT(!! m_pRcvBuffer); if (!m_pRcvBuffer) { LOGP(cnlog.Error, "IPE: ack can't be sent, buffer doesn't exist and no group membership"); @@ -8329,7 +8370,7 @@ bool CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) w_log_reason = "first lost"; else w_log_reason = "expected next"; - + HLOGC(xtlog.Debug, log << CONID() << "NONCONT-SEQUENCE: " << w_log_reason << " %" << w_seq); return true; } @@ -8361,11 +8402,104 @@ int 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 SRT_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. + SharedLock 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. + // XXX Consider adding a method of recognizin the IDLE + // links by having the number of packets received from + // another link exceed some predefined number or time, + // while over the link in question nothing was received. + 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 && !bNeedFullAck) { - HLOGC(xtlog.Debug, - log << CONID() << "sendCtrl(UMSG_ACK): last ACK %" << ack << "(" << reason << ") == last ACKACK"); - return nbsent; + HLOGC(xtlog.Debug, + log << CONID() << "sendCtrlAck: last ACK %" << ack << "(" << reason << ") == last ACKACK; NOT sending."); + return nbsent; } // send out a lite ACK // to save time on buffer processing and bandwidth/AS measurement, a lite ACK only feeds back an ACK number @@ -8374,14 +8508,14 @@ int CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) ctrlpkt.pack(UMSG_ACK, NULL, &ack, size); ctrlpkt.set_id(m_PeerID); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); - 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. + int avail_receiver_buffer_size = 0; #if SRT_ENABLE_BONDING - CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); + if (group_buffering) + avail_receiver_buffer_size = gkeeper.group->getAvailBufSize(ack); #endif // There are new received packets to acknowledge, update related information. @@ -8392,33 +8526,29 @@ int CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) // There are new received packets to acknowledge, update related information. if (CSeqNo::seqcmp(ack, m_iRcvLastAck) > 0) { - // Sanity check if the "selected ACK" points to a sequence - // in the past for the buffer. This SHOULD NEVER HAPPEN because - // on drop the loss records should have been removed, and the last received - // sequence also can't be in the past towards the buffer. + // It would be nice to do a sanity check if this sequence + // isn't in the past for the buffer, but there's no point + // in doing it by two reasons: + // + // 1. In this implementation the alleged ACK sequence is taken + // exclusively from the buffer, so there's no possibility that it + // took a sequence of the loss being in the past towards the buffer + // that was incorrectly removed, which was a problem in the past. + // This implementation doesn't look into the loss record at all. + // 2. Taking the start sequence of the buffer requires checking + // it separately for the socket and for the group, use separate + // mutexes etc., and just to do a sanity check it's not worth + // a shot. - // NOTE: This problem has been observed when the packet sequence - // was incorrectly removed from the receiver loss list. This should - // then stay here as a condition in order to detect this problem, - // should it happen in the future. - if (CSeqNo::seqcmp(ack, m_pRcvBuffer->getStartSeqNo()) < 0) - { - LOGC(xtlog.Error, - log << CONID() << "sendCtrlAck: IPE: invalid ACK from %" << m_iRcvLastAck << " to %" << ack << " (" - << CSeqNo::seqoff(m_iRcvLastAck, ack) << " packets) buffer=%" << m_pRcvBuffer->getStartSeqNo()); - } - else - { - HLOGC(xtlog.Debug, - log << CONID() << "sendCtrlAck: %" << m_iRcvLastAck << " -> %" << ack << " (" - << CSeqNo::seqoff(m_iRcvLastAck, ack) << " packets)"); - } + HLOGC(xtlog.Debug, + log << CONID() << "sendCtrlAck: %" << m_iRcvLastAck << " -> %" << ack << " (" + << CSeqNo::seqoff(m_iRcvLastAck, ack) << " packets)"); m_iRcvLastAck = ack; -#if SRT_ENABLE_BONDING +/* WAS: #if ENABLE_OLD_BONDING (used in the blocked code; to be removed in perspective) const int32_t group_read_seq = m_pRcvBuffer->getFirstReadablePacketInfo(steady_clock::now()).seqno; -#endif +#endif */ InvertedLock un_bufflock (m_RcvBufferLock); @@ -8434,13 +8564,17 @@ int 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. SharedLock glock (uglobal().m_GlobControlLock); // Note that updateLatestRcv will lock m_GroupOf->m_GroupLock, @@ -8450,6 +8584,10 @@ int CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) // 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 that this will only effectively update the positional + // fields for sequence numbers in CUDT entity and nothing else + // because group members don't have their own receiver buffer. m_parent->m_GroupOf->updateLatestRcv(m_parent); } } @@ -8474,9 +8612,21 @@ int CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { CUniqueSync rdcc (m_RecvLock, m_RecvDataCond); +#if SRT_ENABLE_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() + + // 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_pRcvBuffer && isRcvBufferReady()) +#else if (isRcvBufferReady()) +#endif { if (m_config.bSynRecving) { @@ -8484,7 +8634,7 @@ int 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: @@ -8497,7 +8647,21 @@ int CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true); } } -#if SRT_ENABLE_BONDING + + // 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) { // See above explanation for double-checking @@ -8511,7 +8675,8 @@ int CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) m_parent->m_GroupOf->updateReadState(m_SocketID, group_read_seq); } } -#endif + */ + CGlobEvent::triggerEvent(); } } @@ -8522,15 +8687,15 @@ int 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 if (!bNeedFullAck) { // Not possible (m_iRcvCurrSeqNo+1 <% m_iRcvLastAck ?) - LOGC(xtlog.Error, log << CONID() << "sendCtrl(UMSG_ACK): IPE: curr(" << reason << ") %" - << ack << " <% last %" << m_iRcvLastAck); + LOGC(xtlog.Error, log << CONID()<< "sendCtrlAck: IPE: curr(" << reason << ") %" << ack + << " <% last %" << m_iRcvLastAck); return nbsent; } @@ -8544,6 +8709,12 @@ int 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 (in which case this value + // has been already set few lines earlier). + 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, @@ -8553,7 +8724,7 @@ int 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; m_bBufferWasFull = data[ACKD_BUFFERLEFT] == 0; if (steady_clock::now() - m_tsLastAckTime > m_tdACKInterval) { @@ -8590,7 +8761,7 @@ int CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) ctrlpkt.set_id(m_PeerID); setPacketTS(ctrlpkt, steady_clock::now()); nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); - DebugAck(CONID() + "sendCtrl(UMSG_ACK): ", local_prevack, ack); + DebugAck(CONID() + "sendCtrlAck: ", local_prevack, ack); m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck); @@ -8600,37 +8771,118 @@ int 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"); } return nbsent; } -void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) +bool 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 SRT_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 - // Update sender's loss list and acknowledge packets in the sender's buffer + // 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 { + // For groups of that type we use the common loss handling. + + 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); 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 SRT_ENABLE_BONDING - if (is_group) + 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 @@ -8639,14 +8891,77 @@ void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) // Just keep this value prepared; it can't be updated exactly right // now because accessing the group needs some locks to be applied // with preserved the right locking order. + + // 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); + CGlobEvent::triggerEvent(); + } + 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 SRT_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) + { + 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; // 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); + 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 + { +#if SRT_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 + + // 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); @@ -8654,7 +8969,7 @@ void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) } #if SRT_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. @@ -8677,6 +8992,8 @@ void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) } #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 = m_pMuxer->updateSendNormal(m_parent); @@ -8691,6 +9008,8 @@ void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) m_stats.m_sndDurationTotal += count_microseconds(currtime - m_stats.sndDurationCounter); m_stats.sndDurationCounter = currtime; leaveCS(m_StatsLock); + + return valid_sndbuf_revoke; } void CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point& currtime) @@ -8713,9 +9032,21 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point const bool isLiteAck = ctrlpkt.getLength() == (size_t)SEND_LITE_ACK; HLOGC(inlog.Debug, log << CONID() << "ACK covers: " << m_iSndLastDataAck.load() << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck - << "]" << (isLiteAck ? "[LITE]" : "[FULL]")); + << "]" << (isLiteAck ? "[LITE]" : "[FULL]") << " last-sent=%" << m_iSndCurrSeqNo); - 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; + 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) @@ -8725,7 +9056,7 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point { m_iFlowWindowSize = m_iFlowWindowSize - CSeqNo::seqoff(m_iSndLastAck, ackdata_seqno); m_iSndLastAck = ackdata_seqno; - + m_iSndMinFlightSpan = getFlightSpan(); m_tsLastRspAckTime = currtime; m_iReXmitCount = 1; // Reset re-transmit count since last ACK } @@ -8757,15 +9088,29 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point { UniqueLock ack_lock(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(m_iSndCurrSeqNo)) > 0) + if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(last_sent_seqno)) > 0) { ack_lock.unlock(); // 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; setAgentCloseReason(SRT_CLS_IPE); @@ -8780,10 +9125,11 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point const int cwnd1 = std::min(m_iFlowWindowSize, m_iCongestionWindow); const bool bWasStuck = cwnd1 <= getFlightSpan(); // Update Flow Window Size, must update before and together with m_iSndLastAck - m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; - m_iSndLastAck = ackdata_seqno; + 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 const int cwnd = std::min(m_iFlowWindowSize, m_iCongestionWindow); if (bWasStuck && cwnd > getFlightSpan()) @@ -9030,24 +9376,33 @@ void CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival updateCC(TEV_ACKACK, EventVariant(ack)); + bool drift_updated_already = false; + +#if SRT_ENABLE_BONDING + CUDTUnited::GroupKeeper gk (uglobal(), m_parent); + + // 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) + { + drift_updated_already = 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 (!drift_updated_already && m_config.bDriftTracer && m_pRcvBuffer) { -#if SRT_ENABLE_BONDING - ExclusiveLock glock(uglobal().m_GlobControlLock); // XXX not too excessive? - const bool drift_updated = -#endif - m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, rtt); - -#if SRT_ENABLE_BONDING - if (drift_updated && m_parent->m_GroupOf) - m_parent->m_GroupOf->synchronizeDrift(this); -#endif + const bool drift_updated SRT_ATR_UNUSED = m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, rtt); + // XXX Note: drift synchronization code removed here because + // with the new rules of the common receiver buffer the drift + // is handled exclusively in the group and not in the socket at all. +// XXX ??? ExclusiveLock glock(uglobal().m_GlobControlLock); } // Update last ACK that has been received by the sender @@ -9066,13 +9421,37 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) // when logging is forcefully off. int32_t wrong_loss SRT_ATR_UNUSED = SRT_SEQNO_NONE; - // protect packet retransmission { - ScopedLock ack_lock(m_RecvAckLock); +#if SRT_ENABLE_BONDING + // Keep the 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. + // + // Note that below there are applications of the loss + // sequence done either WITH bonding, which require no + // locking, or WITHOUT bonding, which require locking. + // + // XXX Consider complete removal of the non-bonding + // alternative and apply the new bonding-friendly method + // to all cases. In this case this lock would be removed. + + // 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)losslist_len; i < n; ++i) { +#if !SRT_ENABLE_BONDING + int num = 0; // For stats +#endif // IF the loss is a range if (IsSet(losslist[i], LOSSDATA_SEQNO_RANGE_FIRST)) { @@ -9098,13 +9477,16 @@ void 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"); +#if SRT_ENABLE_BONDING + losses.push_back(make_pair(losslist_lo, losslist_hi)); +#else num = m_pSndLossList->insert(losslist_lo, losslist_hi); +#endif } // ELSE losslist_lo %< m_iSndLastAck else @@ -9128,7 +9510,11 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << m_iSndLastAck << "[ACK] - " << losslist_hi << " to loss list"); +#if SRT_ENABLE_BONDING + losses.push_back(make_pair(m_iSndLastAck.load(), losslist_hi)); +#else num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi); +#endif dropreq_hi = CSeqNo::decseq(m_iSndLastAck); IF_HEAVY_LOGGING(drop_type = "partially"); } @@ -9148,9 +9534,6 @@ void 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 the loss is a single seq else @@ -9170,11 +9553,12 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding %" << losslist[i] << " (1 packet) to loss list"); - const int num = m_pSndLossList->insert(losslist[i], losslist[i]); +#if SRT_ENABLE_BONDING + 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); } // ELSE loss_seq %< m_iSndLastAck else @@ -9189,7 +9573,51 @@ void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair)); } } +#if !SRT_ENABLE_BONDING + enterCS(m_StatsLock); + m_stats.sndr.lost.count(num); + leaveCS(m_StatsLock); +#endif + } + +#if SRT_ENABLE_BONDING + 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; + + 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. + if (!m_parent->m_GroupOf->updateSendPacketLoss(d->use_send_schedule, losses)) + { + 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 + } updateCC(TEV_LOSSREPORT, EventVariant(losslist, losslist_len)); @@ -9198,7 +9626,7 @@ void 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; @@ -9365,6 +9793,13 @@ void CUDT::processCtrlDropReq(const CPacket& ctrlpkt) const int32_t* dropdata = (const int32_t*) ctrlpkt.m_pcData; +#if SRT_ENABLE_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. @@ -9515,7 +9950,9 @@ void CUDT::processCtrl(const CPacket &ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" - << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=@" << ctrlpkt.id()); + << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) + << ") socket=@" << ctrlpkt.id() + << " arg=" << ctrlpkt.getAckSeqNo() << "/0x" << hex << ctrlpkt.getAckSeqNo()); switch (ctrlpkt.getType()) { @@ -9576,8 +10013,19 @@ void CUDT::processCtrl(const CPacket &ctrlpkt) } } +// Called only for the old buffer with groups (XXX so might be it's not necessary) void CUDT::updateSrtRcvSettings() { +#if SRT_ENABLE_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 @@ -9591,7 +10039,7 @@ void CUDT::updateSrtRcvSettings() // NOTE: remember to also update synchronizeWithGroup() if more settings are updated here. m_pRcvBuffer->setPeerRexmitFlag(m_bPeerRexmitFlag); - if (m_bTsbPd || m_bGroupTsbPd) + if (m_bTsbPd) { m_pRcvBuffer->setTsbPdMode(m_tsRcvPeerStartTime, false, milliseconds_from(m_iTsbPdDelay_ms)); @@ -9685,7 +10133,7 @@ void CUDT::updateAfterSrtHandshake(int hsv) // XXX During sender buffer refax turn this into either returning // a sequence number or move it to the sender buffer facility. // [[using locked (m_RecvAckLock)]] -pair CUDT::getCleanRexmitOffset() +pair CUDT::getCleanRexmitOffset(int32_t exp_seq SRT_ATR_UNUSED) { // This function is required to look into the loss list and // drop all sequences that are already revoked from the sender @@ -9706,6 +10154,16 @@ pair CUDT::getCleanRexmitOffset() } int offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); +#if SRT_ENABLE_BONDING + if (exp_seq != SRT_SEQNO_NONE && seq != exp_seq) + { + // This is not the sequence we are looking for. + HLOGC(qslog.Debug, log << "packLostData: expected %" << exp_seq << " extracted %" << seq + << " - skipping this."); + continue; + } +#endif + if (offset < 0) { // XXX Likely that this will never be executed because if the upper @@ -9852,7 +10310,7 @@ int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, int offset, CPacket& w_pa } -int CUDT::packLostData(CPacket& w_packet) +int CUDT::packLostData(CPacket& w_packet, int32_t exp_seq) { #ifdef SRT_ENABLE_MAXREXMITBW @@ -9902,7 +10360,7 @@ int CUDT::packLostData(CPacket& w_packet) // Get the first sequence for retransmission, bypassing and taking care of // those that are in the forgotten region, as well as required to be rejected. - Tie(seqno, offset) = getCleanRexmitOffset(); + Tie(seqno, offset) = getCleanRexmitOffset(exp_seq); if (seqno == SRT_SEQNO_NONE) return 0; @@ -10060,7 +10518,7 @@ void CUDT::setPacketTS(CPacket& p, const time_point& ts) enterCS(m_StatsLock); const time_point tsStart = m_stats.tsStartTime; leaveCS(m_StatsLock); - p.set_timestamp(makeTS(ts, tsStart)); + setPacketTS(p, tsStart, ts); } void CUDT::setDataPacketTS(CPacket& p, const time_point& ts) @@ -10072,11 +10530,16 @@ void CUDT::setDataPacketTS(CPacket& p, const time_point& ts) if (!m_bPeerTsbPd) { // If TSBPD is disabled, use the current time as the source (timestamp using the sending time). - p.set_timestamp(makeTS(steady_clock::now(), tsStart)); + setPacketTS(p, tsStart, steady_clock::now()); return; } // TODO: Might be better for performance to ensure this condition is always false, and just use SRT_ASSERT here. + // XXX The condition for having ts always in the future towards tsStart + // can be ensured at the scheduling time, that is, the only possibility exists + // that a user supply a timestamp that is in the past towards the start time. + // The sending function already rejects such a sending request and reports an error. + // When this is rejected, there's no way that ts < tsStart. if (ts < tsStart) { p.set_timestamp(makeTS(steady_clock::now(), tsStart)); @@ -10088,7 +10551,7 @@ void CUDT::setDataPacketTS(CPacket& p, const time_point& ts) } // Use the provided source time for the timestamp. - p.set_timestamp(makeTS(ts, tsStart)); + setPacketTS(p, tsStart, ts); } bool CUDT::isRegularSendingPriority() @@ -10217,6 +10680,8 @@ bool CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, CNe m_tdSendTimeDiff = m_tdSendTimeDiff.load() + (enter_time - m_tsNextSendTime); } + IF_HEAVY_LOGGING(const char* reason); // The source of the data packet (normal/rexmit/filter) + ScopedLock connectguard(m_ConnectionLock); // If a closing action is done simultaneously, then // m_bOpened should already be false, and it's set @@ -10228,50 +10693,290 @@ bool CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, CNe if (!m_bOpened) return false; - // This function returns true if there is a regular packet candidate waiting - // for being sent and sending this packet has a prioriry over retransmission candidate. - if (!isRegularSendingPriority()) +#if SRT_ENABLE_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 (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) { - payload = packLostData((w_packet)); -#ifdef SRT_ENABLE_MAXREXMITBW - if (payload == 0) + // 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 + // the sender buffer. + + // 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 (!gk.group->getSendSchedule(m_parent->m_GroupMemberData, (seqs))) + { + HLOGP(qslog.Debug, "packData(sched): No scheduled packets yet, nothing to send"); + return false; + } + +#if HVU_ENABLE_HEAVY_LOGGING { - HLOGC(qslog.Debug, log << "REXMIT-SH: no rexmit required, remain " << m_SndRexmitShaper.ntokens() << " tokens"); + 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 - } - else - { - HLOGC(qslog.Debug, log << "REXMIT: retransmission SUPERSEDED, proceed with regular candidate"); - } - // Updates the data that will be next used in packLostData() in next calls. - updateSenderMeasurements(payload != 0); + int nremoved = 0; + for (size_t i = 0; i < seqs.size(); ++i) + { + groups::SchedSeq ss = seqs[i]; + w_packet.set_seqno(ss.seq); + if (ss.type == groups::SQT_FRESH) + new_packet_packed = true; - IF_HEAVY_LOGGING(const char* reason); // The source of the data packet (normal/rexmit/filter) - if (payload > 0) - { - IF_HEAVY_LOGGING(reason = "reXmit"); - } - else if (m_PacketFilter && // XXX m_iSndCurrSeqNo requires locking m_RcvAckLock - m_PacketFilter.packControlPacket(m_iSndCurrSeqNo, m_CryptoControl.getSndCryptoFlags(), (w_packet))) - { - HLOGC(qslog.Debug, log << CONID() << "filter: filter/CTL packet ready - packing instead of data."); - payload = (int) w_packet.getLength(); - IF_HEAVY_LOGGING(reason = "filter"); + nremoved = i+1; - // Stats - ScopedLock lg(m_StatsLock); - m_stats.sndr.sentFilterExtra.count(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; + } + else if (ss.type == groups::SQT_LOSS) + { + // 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. + + // 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 = gk.group->packLostData(this, (w_packet), ss.seq); + if (payload == 0) + { + // 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; + } + 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) + { + // 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" 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 + // 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; + + // 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; + } + + 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 + // 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 + + // Roll here until you finally extract the packet with the expected + // sequence. + for (;;) + { + if (!packUniqueData((w_packet))) + { + // 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.seqno() == ss.seq) + { + break; + } + + HLOGC(qslog.Debug, log << "packData(sched): extracted %" << w_packet.seqno() << " while expecting scheduled %" << ss.seq << " - retrying."); + + // This is not the packet we are looking for. + // Fallback, just in case. + if (CSeqNo::seqcmp(w_packet.seqno(), ss.seq) > 0) + { + LOGC(qslog.Debug, log << "packetData(sched): IPE: extraction from the sender buffer found %" << w_packet.seqno() << " past %" << ss.seq); + payload = 0; // force uplevel break + break; + } + } + if (payload == 0) + break; + + new_packet_packed = true; + + // every 16 (0xF) packets, a packet pair is sent + if ((w_packet.seqno() & PUMASK_SEQNO_PROBE) == 0) + probe = true; + + IF_HEAVY_LOGGING(reason = "normal"); + + // The packet was successfully picked up, stop the loop here. + break; + } + else // SQT_SKIP + { + continue; + } + + 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; + } + } + + // 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"); + gk.group->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(); + return false; + } } else +#endif { - if (!packUniqueData(w_packet)) + // This function returns true if there is a regular packet candidate waiting + // for being sent and sending this packet has a prioriry over retransmission candidate. + if (!isRegularSendingPriority()) { - m_tsNextSendTime = steady_clock::time_point(); - m_tdSendTimeDiff = steady_clock::duration(); - return false; +#if SRT_ENABLE_BONDING && 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), SRT_SEQNO_NONE /* get any seq */); + } + else +#endif + payload = packLostData((w_packet)); +#ifdef SRT_ENABLE_MAXREXMITBW + if (payload == 0) + { + HLOGC(qslog.Debug, log << "REXMIT-SH: no rexmit required, remain " << m_SndRexmitShaper.ntokens() << " tokens"); + } +#endif + } + else + { + payload = 0; + } + + // Updates the data that will be next used in packLostData() in next calls. + updateSenderMeasurements(payload != 0); + + if (payload > 0) + { + IF_HEAVY_LOGGING(reason = "reXmit"); } + else if (m_PacketFilter && // XXX m_iSndCurrSeqNo requires locking m_RcvAckLock + m_PacketFilter.packControlPacket(m_iSndCurrSeqNo, m_CryptoControl.getSndCryptoFlags(), (w_packet))) + { + HLOGC(qslog.Debug, log << CONID() << "filter: filter/CTL packet ready - packing instead of data."); + payload = (int) w_packet.getLength(); + IF_HEAVY_LOGGING(reason = "filter"); + + // Stats + ScopedLock lg(m_StatsLock); + m_stats.sndr.sentFilterExtra.count(1); + } + else + { + if (!packUniqueData((w_packet))) + { + m_tsNextSendTime = steady_clock::time_point(); + m_tdSendTimeDiff = steady_clock::duration::zero(); + return false; + } #if SRT_ENABLE_MAXREXMITBW if (m_zSndAveragePacketSize > 0) @@ -10284,14 +10989,15 @@ bool CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, CNe } m_zSndMaxPacketSize = std::max(m_zSndMaxPacketSize, w_packet.getLength()); #endif - new_packet_packed = true; + new_packet_packed = true; - // every 16 (0xF) packets, a packet pair is sent - if ((w_packet.seqno() & PUMASK_SEQNO_PROBE) == 0) - probe = true; + // every 16 (0xF) packets, a packet pair is sent + if ((w_packet.seqno() & PUMASK_SEQNO_PROBE) == 0) + probe = true; - payload = (int) w_packet.getLength(); - IF_HEAVY_LOGGING(reason = "normal"); + payload = (int) w_packet.getLength(); + IF_HEAVY_LOGGING(reason = "normal"); + } } w_packet.set_id(m_PeerID); // Set the destination SRT socket ID. @@ -10304,7 +11010,7 @@ bool CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, CNe #if HVU_ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr() HLOGC(qslog.Debug, - log << CONID() << "packData: " << reason << " packet seq=" << w_packet.seqno() << " (ACK=" << m_iSndLastAck + log << CONID() << "packData: " << reason << " packet %" << w_packet.seqno() << " (ACK=" << m_iSndLastAck << " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << w_packet.MessageFlagStr() << ")"); #endif @@ -10392,8 +11098,8 @@ bool CUDT::packUniqueData(CPacket& w_packet) if (cwnd <= flightspan) { HLOGC(qslog.Debug, - log << CONID() << "packUniqueData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_iCongestionWindow - << ")=" << cwnd << " seqlen=(" << m_iSndLastAck << "-" << m_iSndCurrSeqNo << ")=" << flightspan); + log << CONID() << "packUniqueData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_iCongestionWindow + << ")=" << cwnd << " seqlen=(" << m_iSndLastAck << "-" << m_iSndCurrSeqNo << ")=" << flightspan); return false; } @@ -10427,8 +11133,8 @@ bool CUDT::packUniqueData(CPacket& w_packet) } #if SRT_ENABLE_BONDING - // Fortunately the group itself isn't being accessed. - if (!m_bClosing && m_parent->m_GroupOf) + CUDTUnited::GroupKeeper gk(uglobal(), m_parent); + if (!m_bClosing && gk.group) { const int packetspan = CSeqNo::seqoff(current_sequence_number, w_packet.seqno()); if (packetspan > 0) @@ -10482,12 +11188,16 @@ bool CUDT::packUniqueData(CPacket& w_packet) else if (packetspan < 0) { LOGC(qslog.Error, - log << CONID() << "IPE: packData: SCHEDULING sequence " << w_packet.seqno() + log << CONID() << "IPE: packUniqueData: SCHEDULING sequence " << w_packet.seqno() << " is behind of EXTRACTION sequence " << current_sequence_number << ", 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; } + + 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); } else #endif @@ -10652,12 +11362,24 @@ bool CUDT::overrideSndSeqNo(int32_t seq) int CUDT::checkLazySpawnTsbPdThread() { - const bool need_tsbpd = m_bTsbPd || m_bGroupTsbPd; - if (!need_tsbpd) +#if SRT_ENABLE_BONDING + const bool need_tsbpd = m_bTsbPd; + const bool need_group_tsbpd = m_bGroupTsbPd && !m_bTsbPd; + + // 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)); + +#else + const bool need_tsbpd = m_bTsbPd; + const bool need_group_tsbpd = false; +#endif + if (!need_tsbpd && !need_group_tsbpd) return 0; ScopedLock lock(m_RcvTsbPdStartupLock); - if (!m_RcvTsbPdThread.joinable()) + if (need_tsbpd && !m_RcvTsbPdThread.joinable()) { if (m_bClosing) // Check m_bClosing to protect join() in CUDT::releaseSync(). return -1; @@ -10676,13 +11398,56 @@ int CUDT::checkLazySpawnTsbPdThread() return -1; } +#if SRT_ENABLE_BONDING + if (need_group_tsbpd) + { + SharedLock 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->checkLazySpawnTsbPdThread(); + } +#endif + return 0; } +#if SRT_ENABLE_BONDING +CUDT::time_point CUDT::getPktTsbPdTime(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 CUDT::getPktTsbPdTime(void*, const CPacket& packet) { return m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); } +#endif SRT_ATR_UNUSED static const char *const s_rexmitstat_str[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; @@ -10702,10 +11467,6 @@ int CUDT::handleSocketPacketReception(const vector& incoming, bool& w_ne CUnit * u = *unitIt; CPacket &rpkt = u->m_Packet; const int pktrexmitflag = m_bPeerRexmitFlag ? (rpkt.getRexmitFlag() ? 1 : 0) : 2; - const bool retransmitted = pktrexmitflag == 1; - - bool adding_successful = true; - const int32_t bufidx = CSeqNo::seqoff(bufseq, rpkt.seqno()); IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); @@ -10786,106 +11547,50 @@ int CUDT::handleSocketPacketReception(const vector& incoming, bool& w_ne } } - CRcvBuffer::InsertInfo info = m_pRcvBuffer->insert(u); + bool adding_successful = true; - // 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 this is false, behave as if nothing has been received. + bool incoming_valid = handlePacketDecryption((u->m_Packet)); + if (incoming_valid) + { + CRcvBuffer::InsertInfo info = m_pRcvBuffer->insert(u); - // 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. + // 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 (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; - } - const int buffer_add_result = int(info.result); + // 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 (buffer_add_result < 0) - { - // The insert() result is -1 if at the position evaluated from this packet's - // sequence number 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) + if (info.result == CRcvBuffer::InsertInfo::INSERTED) { - // 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_CryptoControl.decrypt((u->m_Packet)); - u->m_Packet.setRexmitFlag(retransmitted); // Recover the flag. - - if (rc != ENCS_CLEAR) - { - adding_successful = false; - IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); - - // If TSBPD is disabled, then SRT either operates in buffer mode, of in message API without a restriction - // of a single message packet. In that case just dropping a packet is not enough. - // In message mode the whole message has to be dropped. - // However, when decryption fails the message number in the packet cannot be trusted. - // The packet has to be removed from the RCV buffer based on that pkt sequence number, - // and the sequence number itself must go into the RCV loss list. - // See issue ##2626. - SRT_ASSERT(m_bTsbPd); - - // Drop the packet from the receiver buffer. - // The packet was added to the buffer based on the sequence number, therefore sequence number should be used to drop it from the buffer. - // A drawback is that it would prevent a valid packet with the same sequence number, if it happens to arrive later, to end up in the buffer. - const int iDropCnt = m_pRcvBuffer->dropMessage(u->m_Packet.getSeqNo(), u->m_Packet.getSeqNo(), SRT_MSGNO_NONE, CRcvBuffer::DROP_EXISTING); - - const steady_clock::time_point tnow = steady_clock::now(); - ScopedLock lg(m_StatsLock); - m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * rpkt.getLength(), iDropCnt)); - m_stats.rcvr.undecrypted.count(stats::BytesPackets(rpkt.getLength(), 1)); - string why; - if (frequentLogAllowed(FREQLOGFA_ENCRYPTION_FAILURE, tnow, (why))) - { - LOGC(qrlog.Warn, log << CONID() << "Decryption failed (seqno %" << u->m_Packet.getSeqNo() << "), dropped " - << iDropCnt << ". pktRcvUndecryptTotal=" << m_stats.rcvr.undecrypted.total.count() << "." << why); - } -#if SRT_ENABLE_FREQUENT_LOG_TRACE - else - { + // 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; + } + const int buffer_add_result = int(info.result); - LOGC(qrlog.Warn, log << "SUPPRESSED: Decryption failed LOG: " << why); - } -#endif - } + if (buffer_add_result < 0) + { + // The insert() result is -1 if at the position evaluated from this packet's + // sequence number there already is a packet. + // So this packet is "redundant". + IF_HEAVY_LOGGING(exc_type = "UNACKED"); + adding_successful = false; } - else if (m_CryptoControl.kmState().rcv == SRT_KM_S_SECURED) + else { - // Unencrypted packets are not allowed. - const int iDropCnt = m_pRcvBuffer->dropMessage(u->m_Packet.getSeqNo(), u->m_Packet.getSeqNo(), SRT_MSGNO_NONE, CRcvBuffer::DROP_EXISTING); - - const steady_clock::time_point tnow = steady_clock::now(); - ScopedLock lg(m_StatsLock); - m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt* rpkt.getLength(), iDropCnt)); - m_stats.rcvr.undecrypted.count(stats::BytesPackets(rpkt.getLength(), 1)); - string why; - if (frequentLogAllowed(FREQLOGFA_ENCRYPTION_FAILURE, tnow, (why))) - { - LOGC(qrlog.Warn, log << CONID() << "Packet not encrypted (seqno %" << u->m_Packet.getSeqNo() << "), dropped " - << iDropCnt << ". pktRcvUndecryptTotal=" << m_stats.rcvr.undecrypted.total.count() << "."); - } + IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); + excessive = false; } } - - if (adding_successful) + else { - ScopedLock statslock(m_StatsLock); - m_stats.rcvr.recvdUnique.count(u->m_Packet.getLength()); + IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); + adding_successful = false; } #if HVU_ENABLE_HEAVY_LOGGING @@ -10904,9 +11609,9 @@ int CUDT::handleSocketPacketReception(const vector& incoming, bool& w_ne bufinfo << " BUF.s=" << m_pRcvBuffer->capacity() << " avail=" << (int(m_pRcvBuffer->capacity()) - ackidx) - << " buffer=(%" << bufseq - << ":%" << m_iRcvCurrSeqNo // -1 = size to last index - << "+%" << CSeqNo::incseq(bufseq, int(m_pRcvBuffer->capacity()) - 1) + << " buffer=%(" << bufseq + << ":" << m_iRcvCurrSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(bufseq, int(m_pRcvBuffer->capacity()) - 1) << ")"; } @@ -10921,10 +11626,11 @@ int CUDT::handleSocketPacketReception(const vector& incoming, bool& w_ne << rpkt.MessageFlagStr()); #endif - // Decryption should have made the crypto flags EK_NOENC. - // Otherwise it's an error. if (adding_successful) { + ScopedLock statslock(m_StatsLock); + m_stats.rcvr.recvdUnique.count(u->m_Packet.getLength()); + HLOGC(qrlog.Debug, log << CONID() << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.seqno())); @@ -10938,21 +11644,235 @@ int CUDT::handleSocketPacketReception(const vector& incoming, bool& w_ne } } - // 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.seqno(), m_iRcvCurrSeqNo) > 0) + // If not valid, don't even check the incoming sequence number. + // Take it as if nothing was received. + if (incoming_valid) { - m_iRcvCurrSeqNo = rpkt.seqno(); // Latest possible received + // 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.seqno(), m_iRcvCurrSeqNo) > 0) + { + m_iRcvCurrSeqNo = rpkt.seqno(); // Latest possible received + } + else + { + unlose(rpkt); // was BELATED or RETRANSMITTED + w_was_sent_in_order &= 0 != pktrexmitflag; + } } + } + + return 0; +} + +// NOTE: packet is not yet inserted into the buffer. Will be tried, if this +// function returns true. +bool CUDT::handlePacketDecryption(CPacket& packet) +{ + IF_LOGGING(std::string failure); + + const bool packet_encrypted = packet.getMsgCryptoFlags() != EK_NOENC; + const bool encryption_enabled = m_CryptoControl.kmState().rcv == SRT_KM_S_SECURED; + + if (!packet_encrypted) + { + if (!encryption_enabled) + return true; + + IF_LOGGING(failure = "Packet not encrypted"); + } + else // Decrypt it (regardless of the kmstate) + { + // TODO: reset and restore the timestamp if TSBPD is disabled. + // Reset retransmission flag (must be excluded from GCM auth tag). + const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2; + + packet.setRexmitFlag(false); + const EncryptionStatus rc = m_CryptoControl.decrypt((packet)); + packet.setRexmitFlag(pktrexmitflag == 1); // Recover the flag. + + // Decryption should have made the crypto flags EK_NOENC. + // Otherwise it's an error. + if (rc == ENCS_CLEAR && packet.getMsgCryptoFlags() == EK_NOENC) + return true; + + IF_LOGGING(failure = fmtcat("Decryption ", rc == ENCS_FAILED ? "failed" : "unsupported")); + } + + // DECRYPTION FAILED: Just display the error in the logs and update stats. + { + ScopedLock lg(m_StatsLock); + m_stats.rcvr.undecrypted.count(stats::BytesPackets(packet.getLength(), 1)); + } +#if HVU_ENABLE_LOGGING + const steady_clock::time_point tnow = steady_clock::now(); + string why; + if (frequentLogAllowed(FREQLOGFA_ENCRYPTION_FAILURE, tnow, (why))) + { + LOGC(qrlog.Warn, log << CONID() << failure << " (seqno %" << packet.getSeqNo() + << ") -- dropped. pktRcvUndecryptTotal=" << m_stats.rcvr.undecrypted.total.count() << "." << why); + } +#if SRT_ENABLE_FREQUENT_LOG_TRACE + else + { + + LOGC(qrlog.Warn, log << "SUPPRESSED: Decryption failed LOG: " << why); + } +#endif +#endif + return false; +} + +#if SRT_ENABLE_BONDING +bool 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 + + // 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; + bool adding_successful = true; + bool have_loss = false; + IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); + + bool incoming_valid = handlePacketDecryption((u->m_Packet)); + if (incoming_valid) + { + // 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 + CRcvBuffer::InsertInfo info = grp->addDataUnit(m_parent->m_GroupMemberData, u, (w_srt_loss_seqs), (have_loss)); + + if (info.result == CRcvBuffer::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.seqno()); + } + + // IGNORE remaining packets + return false; + } + + 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( + 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.seqno() << " (BELATED/" + << s_rexmitstat_str[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 == CRcvBuffer::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; + } + } + else + { + IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); + adding_successful = false; + } + +#if HVU_ENABLE_HEAVY_LOGGING + hvu::ofmtbufstream 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.seqno() + << " RSL=" << expectspec + << " SN=" << s_rexmitstat_str[pktrexmitflag] + << " FLAGS: " + << rpkt.MessageFlagStr()); +#endif + + if (adding_successful) { - unlose(rpkt); // was BELATED or RETRANSMITTED - w_was_sent_in_order &= 0 != pktrexmitflag; + HLOGC(qrlog.Debug, + log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.seqno())); + + if (have_loss) + { + HLOGC(qrlog.Debug, log << "grp/LOSS DETECTED: " << FormatLossArray(w_srt_loss_seqs)); + } + + ScopedLock statslock(m_StatsLock); + m_stats.rcvr.recvdUnique.count(u->m_Packet.getLength()); + } + + if (incoming_valid) + { + // 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.seqno(), m_iRcvCurrSeqNo) > 0) + { + m_iRcvCurrSeqNo = rpkt.seqno(); // Latest possible received + } + else + { + unlose(rpkt); // was BELATED or RETRANSMITTED + w_was_sent_in_order &= 0 != pktrexmitflag; + } } } - return 0; + return true; } +#endif int CUDT::processData(CUnit* in_unit) { @@ -10965,6 +11885,11 @@ int CUDT::processData(CUnit* in_unit) m_iEXPCount = 1; m_tsLastRspTime.store(steady_clock::now()); // XXX Requires lock m_RecvAckLock + // Keep the group alive until the end of this function. + +#if SRT_ENABLE_BONDING + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); +#endif // We are receiving data, start tsbpd thread if TsbPd is enabled if (-1 == checkLazySpawnTsbPdThread()) @@ -10974,45 +11899,36 @@ int CUDT::processData(CUnit* in_unit) const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2; const bool retransmitted = pktrexmitflag == 1; -#if HVU_ENABLE_HEAVY_LOGGING - string rexmit_reason; -#endif - if (retransmitted) + if (pktrexmitflag == 1) { // This packet was retransmitted enterCS(m_StatsLock); m_stats.rcvr.recvdRetrans.count(packet.getLength()); leaveCS(m_StatsLock); -#if HVU_ENABLE_HEAVY_LOGGING - // Check if packet was retransmitted on request or on ack timeout - // Search the sequence in the loss record. - rexmit_reason = " by "; - ScopedLock lock(m_RcvLossLock); - if (!m_pRcvLossList->find(packet.seqno(), packet.seqno())) - rexmit_reason += "BLIND"; - else - rexmit_reason += "NAKREPORT"; -#endif } #if HVU_ENABLE_HEAVY_LOGGING - { - steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) + { + 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; + // 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. + time_point pts; +#if SRT_ENABLE_BONDING + pts = getPktTsbPdTime(gkeeper.group, packet); +#else + pts = getPktTsbPdTime(NULL, packet); +#endif + 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)); - } + HLOGC(qrlog.Debug, log << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() + << " seq=" << packet.getSeqNo() + << " ETS=" << FormatTime(ets) + << " PTS=" << FormatTime(pts) + << " NOW=" << FormatTime(m_tsLastRspTime.load())); + } #endif updateCC(TEV_RECEIVE, EventVariant(&packet)); @@ -11066,7 +11982,10 @@ int CUDT::processData(CUnit* in_unit) // 1 - subsequent packet (alright) // <0 - belated or recovered packet // >1 - jump over a packet loss (loss = seqdiff-1) - if (diff > 1) + + // Hook on non-NULL receiver buffer for a case of the common group buffer. + // 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 @@ -11096,9 +12015,9 @@ int 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) { - ExclusiveLock protect_group_existence (uglobal().m_GlobControlLock); + // NO GROUP LOCKING: group is preserved by the Keeper groups::SocketData* gi = m_parent->m_GroupMemberData; // This check is needed as after getting the lock the socket @@ -11113,6 +12032,7 @@ int CUDT::processData(CUnit* in_unit) log << CONID() << "processData: IN-GROUP rcv state transition " << srt_log_grp_state[gi->rcvstate] << " -> RUNNING."); gi->rcvstate = SRT_GST_RUNNING; + gkeeper.group->updateRcvRunningState(); } else { @@ -11144,6 +12064,50 @@ int CUDT::processData(CUnit* in_unit) incoming.push_back(in_unit); } +#if SRT_ENABLE_BONDING + if (gkeeper.group) + { + // Needed for possibly check for needsQuickACK. + const bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.seqno(), gkeeper.group->getOldestRcvSeqNo()) < 0); + + bool handled = handleGroupPacketReception(gkeeper.group, + incoming, + (was_sent_in_order), + (srt_loss_seqs)); + + // 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 (!handled) + return -1; + + // 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)) + { + m_tsNextACKTime.store(steady_clock::now()); + } + } + + // 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 @@ -11395,32 +12359,8 @@ int 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 CUDT::updateIdleLinkFrom(CUDT* source) +void srt::CUDT::updateIdleLinkFrom(int32_t new_last_rcv, SRTSOCKET id SRT_ATR_UNUSED /* logging only */) { - int bufseq; - { - ScopedLock lg (source->m_RcvBufferLock); - bufseq = source->m_pRcvBuffer->getStartSeqNo(); - } - ScopedLock lg (m_RecvLock); - - if (!m_pRcvBuffer->empty()) - { - HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq in @" << m_SocketID << ": receiver buffer not empty"); - return; - } - - 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) { @@ -11432,7 +12372,7 @@ void 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); } @@ -11548,6 +12488,38 @@ void CUDT::unlose(const CPacket &packet) } } +// This is necessary to be called from the group that uses common receiver buffer, +// after receiving a packet from any of the sockets. + +#if SRT_ENABLE_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. +// XXX No longer true and unknown as to whether this function is still needed. +// Check later. +void CUDT::skipMemberLoss(int32_t seqno) +{ + // This will automatically drop all loss items that are + // earlier than seqno, even if seqno is past ACK and past + // the buffer. + + dropFromLossLists(SRT_SEQNO_NONE, 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 CUDT::dropFromLossLists(int32_t from, int32_t to) { ScopedLock lg(m_RcvLossLock); @@ -12464,7 +13436,10 @@ void 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); } @@ -12676,7 +13651,7 @@ bool CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShake& hs return true; } -void CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival) +void 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. @@ -12696,15 +13671,19 @@ void CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival) // Whether anything is to be done with this socket // about the fact that keepalive arrived, let the // group handle it - pg->processKeepalive(m_parent->m_GroupMemberData); + pg->processKeepalive(m_parent->m_GroupMemberData, ctrlpkt, tsArrival); } } #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 ScopedLock lck(m_RcvBufferLock); m_pRcvBuffer->updateTsbPdTimeBase(ctrlpkt.getMsgTimeStamp()); if (m_config.bDriftTracer) m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, -1); +#endif } // This function should be called when closing the socket internally. diff --git a/srtcore/core.h b/srtcore/core.h index 93daf23e2..82c8c1304 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -660,9 +660,9 @@ class CUDT void sendRendezvousRejection(const sockaddr_any& serv_addr, CPacket& request); /// Create the CryptoControl object based on the HS packet. - SRT_ATR_NODISCARD - SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) - bool prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException* eout); +// SRT_ATR_NODISCARD +// SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) +// XXX ? bool prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException* eout); /// Allocates sender and receiver buffers and loss lists. SRT_ATR_NODISCARD @@ -737,7 +737,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. @@ -822,6 +822,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*/); @@ -882,7 +884,9 @@ class CUDT SRT_TSA_NEEDS_NONLOCKED(m_RcvLossLock) // will scope-lock it inside void dropFromLossLists(int32_t from, int32_t to); - +#if SRT_ENABLE_BONDING + void skipMemberLoss(int32_t seqno); +#endif SRT_TSA_NEEDS_NONLOCKED(m_RcvBufferLock) // SRT_TSA_NEEDS_LOCKED(m_RecvAckLock) // <<-- XXX levaing now, but must be investigated bool getFirstNoncontSequence(int32_t& w_seq, std::string& w_log_reason); @@ -1144,6 +1148,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 @@ -1319,16 +1325,23 @@ 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); + 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 /// @return payload size on success, <=0 on failure - int packLostData(CPacket &packet); + int packLostData(CPacket &packet, int32_t exp_seq = SRT_SEQNO_NONE); - std::pair getCleanRexmitOffset(); + std::pair getCleanRexmitOffset(int32_t exp_seq); bool checkRexmitRightTime(int offset, const sync::steady_clock::time_point& current_time); int extractCleanRexmitPacket(int32_t seqno, int offset, CPacket& w_packet, sync::steady_clock::time_point& w_tsOrigin); @@ -1348,6 +1361,7 @@ class CUDT /// @retval true A packet was extracted for sending, the socket should be rechecked at @a nexttime /// @retval false Nothing was extracted for sending, @a nexttime should be ignored bool packData(CPacket& packet, time_point& nexttime, CNetworkInterface& src_addr); + void removeSndLossUpTo(int32_t seq); /// Also excludes srt::CUDTUnited::m_GlobControlLock. SRT_TSA_NEEDS_NONLOCKED(m_RcvTsbPdStartupLock, m_StatsLock, m_RecvLock, m_RcvLossLock, m_RcvBufferLock) @@ -1360,24 +1374,43 @@ 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_next_tsbpd [out] Set to the time of the earliest TSBPD-ready packet /// @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). - SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) // will lock inside + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) int handleSocketPacketReception(const std::vector& incoming, bool& w_new_inserted, time_point& w_next_tsbpd, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); + /// Check if the packet SHOULD BE decrypted, and decrypt if if needed. + /// False is returned if: + /// * The packet is not encrypted, while KMSTATE is SECURED ("should be" encrypted) + /// * The decryption process failed + /// + /// @param w_packet Packet to be possibly decrypted + /// @return True if the packet need not be decrypted, or was successfully decrypted. + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) + bool handlePacketDecryption(CPacket& w_packet); + +#if SRT_ENABLE_BONDING + bool handleGroupPacketReception(CUDTGroup* grp, const std::vector& incoming, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); +#endif + // This function is to return the packet's play time (time when // it is submitted to the reading application) of the given packet. - /// The @a grp passed by void* is not used yet - /// and shall not be used when SRT_ENABLE_BONDING=0. + // The version with `void*` is provided because the function body + // is mostly common for bonding and non-bonding, while it needs + // access to groups anyway, if present, and gets NULL in worst case. +#if SRT_ENABLE_BONDING + time_point getPktTsbPdTime(CUDTGroup* grp, const CPacket& packet); +#else time_point getPktTsbPdTime(void* grp, const CPacket& packet); +#endif - SRT_TSA_NEEDS_NONLOCKED(m_RcvTsbPdStartupLock) /// Checks and spawns the TSBPD thread if required. + SRT_TSA_NEEDS_NONLOCKED(m_RcvTsbPdStartupLock) int checkLazySpawnTsbPdThread(); void processClose(); diff --git a/srtcore/fec.cpp b/srtcore/fec.cpp index c3b36603e..4d15f6edf 100644 --- a/srtcore/fec.cpp +++ b/srtcore/fec.cpp @@ -738,7 +738,7 @@ void FECFilterBuiltin::PackControl(const Group& g, signed char index, SrtPacket& + g.payload_clip.size(); // Sanity -#if ENABLE_DEBUG +#if SRT_ENABLE_DEBUG if (g.output_buffer.size() < total_size) { LOGC(pflog.Fatal, log << "OUTPUT BUFFER TOO SMALL!"); diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 0789a0131..4b420883e 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -4,6 +4,8 @@ #include "api.h" #include "group.h" +#include "socketconfig.h" +#include "hvu_threadname.h" using namespace std; using namespace srt::sync; @@ -15,54 +17,13 @@ extern const int32_t SRT_DEF_VERSION; namespace srt { +// A factor for picking up measurements for flight window +const int RANDOM_CREDIT_FACTOR = 16; sync::atomic CUDTGroup::s_tokenGen ( 0 ); static inline char fmt_onoff(bool val) { return val ? '+' : '-'; } -// [[using locked(this->m_GroupLock)]]; -bool CUDTGroup::getBufferTimeBase(CUDT* forthesakeof, - steady_clock::time_point& w_tb, - bool& w_wp, - steady_clock::duration& w_dr) -{ - CUDT* master = 0; - for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) - { - CUDT* u = &gi->ps->core(); - if (gi->laststatus != SRTS_CONNECTED) - { - HLOGC(gmlog.Debug, - log << "getBufferTimeBase: skipping @" << u->m_SocketID - << ": not connected, state=" << SockStatusStr(gi->laststatus)); - continue; - } - - if (u == forthesakeof) - continue; // skip the member if it's the target itself - - if (!u->m_pRcvBuffer) - continue; // Not initialized yet - - master = u; - break; // found - } - - // We don't have any sockets in the group, so can't get - // the buffer timebase. This should be then initialized - // the usual way. - if (!master) - return false; - - master->m_pRcvBuffer->getInternalTimeBase((w_tb), (w_wp), (w_dr)); - // Sanity check - if (is_zero(w_tb)) - { - LOGC(gmlog.Error, log << "IPE: existing previously socket has no time base set yet!"); - return false; // this will enforce initializing the time base normal way - } - return true; -} // [[using locked(this->m_GroupLock)]]; bool CUDTGroup::applyGroupSequences(SRTSOCKET target, int32_t& w_snd_isn, int32_t& w_rcv_isn) @@ -253,8 +214,10 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) : m_Global(CUDT::uglobal()) , m_GroupID(SRT_INVALID_SOCK) , m_PeerGroupID(SRT_INVALID_SOCK) + , m_zLongestDistance(0) , m_type(gtype) , 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) @@ -270,33 +233,123 @@ 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_bOPT_DriftTracer(true) , m_tsStartTime() , m_tsRcvPeerStartTime() - , m_RcvBaseSeqNo(SRT_SEQNO_NONE) , m_bOpened(false) , m_bConnected(false) , m_bClosing(false) , m_iLastSchedSeqNo(SRT_SEQNO_NONE) , m_iLastSchedMsgNo(SRT_MSGNO_NONE) + , m_uBalancingRoll(0) + , m_RandomCredit(RANDOM_CREDIT_FACTOR * 1) { setupMutex(m_GroupLock, "Group"); setupMutex(m_RcvDataLock, "G/RcvData"); setupCond(m_RcvDataCond, "G/RcvData"); - m_RcvEID = m_Global.m_EPoll.create(&m_RcvEpolld); + setupCond(m_RcvTsbPdCond, "G/TSBPD"); + setupMutex(m_RcvBufferLock, "G/Buffer"); + m_SndEID = m_Global.m_EPoll.create(&m_SndEpolld); - HLOGC(gmlog.Debug, log << "Group internal EID: R:E" << m_RcvEID << " W:E" << m_SndEID); + HLOGC(gmlog.Debug, log << "Group internal EID: W:E" << m_SndEID); m_stats.init(); // 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); + + m_RcvFurthestPacketTime = steady_clock::now(); +} + +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(); + + m_pRcvBuffer.reset(new srt::CRcvBuffer(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())); + } + + m_pSndLossList.reset(new CSndLossList(flow_winsize * 2)); +} + +/// Update the internal state after a single link has been switched to RUNNING state. +// [[using locked(m_GroupLock)]] +void CUDTGroup::updateRcvRunningState() +{ + size_t nrunning = 0; + 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); +} + +// [[using locked(m_GroupLock)]] +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(); +} + +// 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 + // 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); + + } CUDTGroup::~CUDTGroup() { - srt_epoll_release(m_RcvEID); + if (m_RcvTsbPdThread.joinable()) + { + // This SHOULD NOT happen, but let's just stay safe. + LOGC(gmlog.Fatal, log << "IPE: GLat thread should be already stopped when deleting a group!!!"); + m_RcvTsbPdThread.join(); + } + srt_epoll_release(m_SndEID); releaseMutex(m_GroupLock); releaseMutex(m_RcvDataLock); @@ -390,11 +443,50 @@ 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_DRIFTTRACER: + { + m_bOPT_DriftTracer = cast_optval(optval, optlen); + return; // no passthru. + } case SRTO_GROUPMINSTABLETIMEO: { @@ -428,6 +520,10 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) break; + case SRTO_GROUPCONFIG: + configure((const char*)optval); + return; + default: break; } @@ -601,6 +697,10 @@ void CUDTGroup::deriveSettings(CUDT* u) IM(SRTO_OHEADBW, iOverheadBW); IM(SRTO_IPTOS, iIpToS); IM(SRTO_IPTTL, iIpTTL); + + // XXX CONTROVERSIAL: there must be either the whole group TSBPD or not. + // Single sockets should not have a say there. And also currently the + // groups are not prepared to handle non-tsbpd mode. IM(SRTO_TSBPDMODE, bTSBPD); IM(SRTO_RCVLATENCY, iRcvLatency); IM(SRTO_PEERLATENCY, iPeerLatency); @@ -621,6 +721,7 @@ void CUDTGroup::deriveSettings(CUDT* u) importStringOption(m_config, SRTO_PACKETFILTER, u->m_config.sPacketFilterConfig); + // XXX reaching out to m_pCryptoControl here can be racy. importTrivialOption(m_config, SRTO_PBKEYLEN, (int) u->m_CryptoControl.keylen()); // Passphrase is empty by default. Decipher the passphrase and @@ -740,7 +841,7 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) RD(true); case SRTO_MAXBW: RD(int64_t(-1)); -#ifdef ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW case SRTO_MAXREXMITBW: RD(int64_t(-1)); #endif @@ -1029,7 +1130,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) { @@ -1040,16 +1141,451 @@ void CUDTGroup::syncWithSocket(const CUDT& core, const HandshakeSide side) set_currentSchedSequence(core.ISN()); } - // Only set if was not initialized to avoid problems on a running connection. - if (m_RcvBaseSeqNo == SRT_SEQNO_NONE) - m_RcvBaseSeqNo = CSeqNo::decseq(core.m_iPeerISN); - + // 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 current socket. + // and set it on the group. set_latency_us(core.m_iTsbPdDelay_ms * int64_t(1000)); + + /* + 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. + */ + + // Only set if was not initialized to avoid problems on a running connection. + int32_t butlast_seqno = CSeqNo::decseq(core.ISN()); + // XXX NEEDED? if (m_RcvBaseSeqNo == SRT_SEQNO_NONE) + 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) + { + 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, core.m_iFlowWindowSize); +} + +CRcvBuffer::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 + + CRcvBuffer::InsertInfo info; + const CPacket& rpkt = u->m_Packet; + w_have_loss = false; + + { + ScopedLock lk (m_RcvBufferLock); + info = m_pRcvBuffer->insert(u); + + if (info.result == CRcvBuffer::InsertInfo::INSERTED) + { + w_have_loss = checkPacketArrivalLoss(member, rpkt, (w_losses)); + } + } + + if (info.result == CRcvBuffer::InsertInfo::INSERTED) + { + // 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_bTsbPd && (m_bTsbpdWaitForNewPacket || info.first_time != time_point())) + { + 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); + tsbpd_cc.notify_all(); + } + } + else if (info.result == CRcvBuffer::InsertInfo::DISCREPANCY) + { + ScopedLock lk (m_RcvBufferLock); + LOGC(qrlog.Error, log << CONID() << "grp/addDataUnit: " + << "SEQUENCE DISCREPANCY. DISCARDING." + << " seq=" << rpkt.seqno() + << " buffer=(" << m_pRcvBuffer->getStartSeqNo() + << ":" << m_RcvLastSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(m_pRcvBuffer->getStartSeqNo(), int(m_pRcvBuffer->capacity()) - 1) + << ")"); + } + else + { +#if HVU_ENABLE_HEAVY_LOGGING + // XXX Consider generlizing this display value. + 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; +} + +// [[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()) + { + // 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; + + // Skipping the sequence number of the new contiguous region + std::pair drop = m_pRcvBuffer->dropUpTo(seqno); + + // XXX Temporary solution, but these numbers were split for a reason. + iDropCnt = drop.first + drop.second; + + /* XXX 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); + } + */ + } + + 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(); + // XXX Consider expanding this call in place. + u.skipMemberLoss(seqno); + } +} + +// [[using locked(m_RcvBufferLock)]] +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: + + // - 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; + + // 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. + // + // 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.seqno(), expected_seqno) > 0) + { + int32_t seqlo = expected_seqno; + int32_t seqhi = CSeqNo::decseq(rpkt.seqno()); + + w_losses.push_back(make_pair(seqlo, seqhi)); + have = true; + HLOGC(grlog.Debug, log << "grp:checkPacketArrivalLoss: loss detected: %(" + << seqlo << " - " << seqhi << ")"); + } + + if (CSeqNo::seqcmp(rpkt.seqno(), m_RcvLastSeqNo) > 0) + { + HLOGC(grlog.Debug, log << "grp:checkPacketArrivalLoss: latest updated: %" << m_RcvLastSeqNo << " -> %" << rpkt.seqno()); + m_RcvLastSeqNo = rpkt.seqno(); + + // 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.seqno(), m_RcvLastSeqNo); + dist = max(m_zLongestDistance, dist); + m_zLongestDistance = dist; + + duration td = steady_clock::now() - m_RcvFurthestPacketTime.load(); + td = max(m_tdLongestDistance.load(), td); + m_tdLongestDistance = td; + + HLOGC(grlog.Debug, log << "grp:checkPacketArrivalLoss: latest = %" << m_RcvLastSeqNo << ": pkt %" << rpkt.seqno() + << " dist={" << dist << "pkt " << FormatDuration(m_tdLongestDistance) << (updated ? "} (reflected)" : "} (continued)")); + } + + return have; +} + +struct FFringeGreaterThan +{ + size_t baseval; + FFringeGreaterThan(size_t b): baseval(b) {} + + template + bool operator()(const pair& val) + { + return val.second > baseval; + } +}; + +// [[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); + + 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.seqno(), 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; + + // 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: + // + // 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 HVU_ENABLE_HEAVY_LOGGING + const char* which_condition[3] = {"fullcover", "longtail", "both???"}; +#endif + + 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; + 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; + + // Save the next found loss + m_iRcvPossibleLossSeq = m_pRcvBuffer->getFirstLossSeq(CSeqNo::incseq(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) + { + // We extracted all losses + more_losses = false; + break; + } + + // Found at least one reportable loss + more_losses = true; + continue; + } + else + { + HLOGC(gmlog.Debug, log << "... not yet a loss - waiting for possible sealing"); + } + + 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.seqno(), 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.seqno())); + w_losses.push_back(loss); + HLOGC(gmlog.Debug, log << "grp:checkBalancingLoss: incom %" << pkt.seqno() << " jumps over expected %" << next_seqno + << " - with 1 link only, just reporting"); + return true; + } + + HLOGC(gmlog.Debug, log << "grp:checkBalancingLoss: incom %" << pkt.seqno() << " jumps over expected %" << next_seqno + << " - setting up as loss candidate"); + m_iRcvPossibleLossSeq = next_seqno; + } + + 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"; + + HLOGC(xtlog.Debug, log << CONID() << "NONCONT-SEQUENCE: " << w_log_reason << " %" << w_seq); + + return true; } + void CUDTGroup::close() { // Close all descriptors, then delete the group. @@ -1070,7 +1606,7 @@ void CUDTGroup::close() CUDTSocket* s = CUDT::uglobal().locateSocket_LOCKED(ig->id); if (!s) { - HLOGC(smlog.Debug, log << "group/close: IPE(NF): group member @" << ig->id << " already deleted"); + LOGC(smlog.Error, log << "group/close: IPE(NF): group member @" << ig->id << " already deleted"); continue; } @@ -1159,7 +1695,16 @@ void CUDTGroup::close() // XXX This looks like a dead code. Group receiver functions // do not use any lock on m_RcvDataLock, it is likely a remainder // of the old, internal implementation. - // CSync::lock_notify_one(m_RcvDataCond, m_RcvDataLock); + // XXX Not exactly; looks like CUDTGroup::recv uses it. + { + ScopedLock lk(m_RcvDataLock); + m_RcvTsbPdCond.notify_all(); + m_RcvDataCond.notify_all(); + } + + // The loop of m_RcvTsbPdThread should exit after setting m_bClosing. + if (m_RcvTsbPdThread.joinable()) + m_RcvTsbPdThread.join(); } // [[using locked(m_Global.m_GlobControlLock)]] @@ -1205,11 +1750,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)); */ @@ -1217,6 +1761,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 SRT_ATR_UNUSED) { // Avoid stupid errors in the beginning. if (len <= 0) @@ -1281,7 +1835,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) 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; } @@ -1291,7 +1845,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) 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; @@ -1320,7 +1874,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) 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 @@ -1334,13 +1888,13 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) 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); @@ -1420,7 +1974,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) // 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; @@ -1432,7 +1986,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) 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); @@ -1440,7 +1994,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) 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)); } @@ -1480,7 +2034,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) 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; } @@ -1509,7 +2063,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) if (!pendingSockets.empty() || nblocked) { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: found pending sockets (blocked: " << nblocked << "), polling them."); + HLOGC(gslog.Debug, log << "grp/sendSelectable: found pending sockets (blocked: " << nblocked << "), polling them."); // These sockets if they are in pending state, they should be added to m_SndEID // at the connecting stage. @@ -1519,7 +2073,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) { // 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 @@ -1532,7 +2086,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) // If this is the case when if (m_bSynSending && is_pending_blocked) { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: will block for " << m_iSndTimeOut << " - waiting for any writable in blocking mode"); + HLOGC(gslog.Debug, log << "grp/sendSelectable: will block for " << m_iSndTimeOut << " - waiting for any writable in blocking mode"); swait_timeout = m_iSndTimeOut; } @@ -1551,7 +2105,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) 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. // IMPORTANT: we check only PENDING sockets (not blocked) because only @@ -1563,7 +2117,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) 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; @@ -1599,7 +2153,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) // } - // { 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 @@ -1629,7 +2183,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) { 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, @@ -1686,7 +2240,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) // 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); } @@ -1726,7 +2280,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) 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). @@ -1748,7 +2302,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) { // 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(); @@ -1856,12 +2410,12 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); if (!m_bSynSending && (is_pending_blocked || was_blocked)) { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: no links are ready for sending"); + HLOGC(gslog.Debug, log << "grp/sendSelectable: no links are ready for sending"); ercode = SRT_EASYNCSND; } else { - 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_ERR, true); } @@ -2104,6 +2658,10 @@ struct FLookupSocketWithEvent_LOCKED } }; + +// Old unused procedure. +// Leaving here for historical reasons. +#if 0 void CUDTGroup::recv_CollectAliveAndBroken(vector& alive, set& broken) { #if HVU_ENABLE_HEAVY_LOGGING @@ -2355,6 +2913,7 @@ int32_t CUDTGroup::getRcvBaseSeqNo() ScopedLock lg(m_GroupLock); return m_RcvBaseSeqNo; } +#endif void CUDTGroup::updateWriteState() { @@ -2362,6 +2921,7 @@ void CUDTGroup::updateWriteState() 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). /// @@ -2399,7 +2959,7 @@ static bool isValidSeqno(int32_t iBaseSeqno, int32_t iPktSeqno) return false; } -int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) +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); @@ -2610,6 +3170,8 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } +#endif // block by if 0 + const char* CUDTGroup::StateStr(CUDTGroup::GroupState st) { static const char* const states[] = {"PENDING", "IDLE", "RUNNING", "BROKEN"}; @@ -2620,40 +3182,6 @@ const char* CUDTGroup::StateStr(CUDTGroup::GroupState st) return unknown; } -void CUDTGroup::synchronizeDrift(const CUDT* srcMember) -{ - SRT_ASSERT(srcMember != NULL); - ScopedLock glock(m_GroupLock); - if (m_Group.size() <= 1) - { - HLOGC(grlog.Debug, log << "GROUP: synch uDRIFT NOT DONE, no other links"); - return; - } - - steady_clock::time_point timebase; - steady_clock::duration udrift(0); - bool wrap_period = false; - srcMember->m_pRcvBuffer->getInternalTimeBase((timebase), (wrap_period), (udrift)); - - HLOGC(grlog.Debug, - log << "GROUP: synch uDRIFT=" << FormatDuration(udrift) << " TB=" << FormatTime(timebase) << "(" - << (wrap_period ? "" : "NO ") << "wrap period)"); - - // Now that we have the minimum timebase and drift calculated, apply this to every link, - // INCLUDING THE REPORTER. - - for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) - { - // Skip non-connected; these will be synchronized when ready - if (gi->laststatus != SRTS_CONNECTED) - continue; - CUDT& member = gi->ps->core(); - if (srcMember == &member) - continue; - - member.m_pRcvBuffer->applyGroupDrift(timebase, wrap_period, udrift); - } -} void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) { @@ -2702,6 +3230,258 @@ void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) } } +// The REAL version for the new group receiver. +// +int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) +{ + CUniqueSync tscond (m_RcvDataLock, m_RcvTsbPdCond); + + /* 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"); + + int as_result = 0; + { + ScopedLock lk (m_RcvBufferLock); + bool ready = m_pRcvBuffer->isRcvDataReady(steady_clock::now()); + + if (ready) + { + 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; + + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPd) + { + HLOGP(tslog.Debug, "SIGNAL TSBPD thread to schedule wakeup FOR EXIT"); + 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); + + int as_result = 0; + { + ScopedLock lk (m_RcvBufferLock); + bool ready = m_pRcvBuffer->isRcvDataReady(steady_clock::now()); + + if (ready) + { + 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); + + if (res == 0) + { + // read is not available any more + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPd) + { + HLOGC(arlog.Debug, log << "grp:recv: nothing to read, SIGNAL TSBPD (" << (m_bTsbpdWaitForExtraction ? "" : "un") << "expected), 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) + { + 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 + { + 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 && !isRcvBufferReady()) + { + /* 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. + HLOGC(tslog.Debug, log << CONID() << "grp:recv: SIGNAL TSBPD" << (m_bTsbpdWaitForNewPacket ? " (spurious)" : "")); + 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); + + { + ScopedLock lk (m_GroupLock); + fillGroupData((w_mctrl), w_mctrl); + } + + + 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: SIGNAL 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; +} + /// @brief Compares group members by their weight (higher weight comes first). struct FCompareByWeight { @@ -2813,7 +3593,8 @@ class StabilityTracer std::string str_tnow = FormatTimeSys(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"; @@ -4142,6 +4923,7 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) return stat; } +// XXX DEAD CODE. // [[using locked(CUDTGroup::m_GroupLock)]]; void CUDTGroup::ackMessage(int32_t msgno) { @@ -4177,7 +4959,7 @@ void CUDTGroup::ackMessage(int32_t msgno) m_iSndAckedMsgNo = msgno; } -void CUDTGroup::processKeepalive(CUDTGroup::SocketData* gli) +void CUDTGroup::processKeepalive(CUDTGroup::SocketData* gli, const CPacket& ctrlpkt SRT_ATR_UNUSED, const time_point& tsArrival SRT_ATR_UNUSED) { // received keepalive for that group member // In backup group it means that the link went IDLE. @@ -4212,6 +4994,12 @@ void CUDTGroup::processKeepalive(CUDTGroup::SocketData* gli) log << "GROUP: received KEEPALIVE in @" << gli->id << " active=PAST - link turning snd=IDLE"); } } + + ScopedLock lck(m_RcvBufferLock); + m_pRcvBuffer->updateTsbPdTimeBase(ctrlpkt.getMsgTimeStamp()); + if (m_bOPT_DriftTracer) + m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, -1); + } void CUDTGroup::internalKeepalive(SocketData* gli) @@ -4250,6 +5038,15 @@ void CUDTGroup::setGroupConnected() } } +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); +} + void CUDTGroup::updateLatestRcv(CUDTSocket* s) { // Currently only Backup groups use connected idle links. @@ -4310,9 +5107,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()); } } @@ -4435,6 +5239,1193 @@ void CUDTGroup::updateFailedLink() } } +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 (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 + 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) + { + 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(); + } + + // 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 " + << RANDOM_CREDIT_FACTOR << " * n_links=" << 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 = RANDOM_CREDIT_FACTOR * number_links; + + goto ReportLink; + } + flight += 2; // prevent having 0 used for equations + + total_flight += flight; + LinkCapableData lcd = {li, flight}; + linkdata.push_back(lcd); + } + + 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; +} + +#if 0 // Old balancing sending, leaving for historical reasons +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); + updateErasedLink(); + 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; +} +#endif + + + + +// Update on adding a new fresh packet to the sender buffer. +// [[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(), 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. + } + } + + 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(this_seq_as_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(bool use_send_sched, const std::vector< std::pair >& seqlist) +{ + ScopedLock lg (m_LossAckLock); + + typedef std::vector< std::pair > seqlist_t; + + 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) + { + 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 gg (m_GroupLock); + + 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. + 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); + SchedSeq scheduled_loss = {seq, groups::SQT_LOSS}; + selink->send_schedule.push_back(scheduled_loss); + lstate.ilink = selink; + } + } + + m_Group.set_active(lstate.ilink); + } + return true; +} + +void CUDTGroup::updateOnACK(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, 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; + IF_HEAVY_LOGGING(const char* as = "FIRST FOUND"); + if (exp_seq == SRT_SEQNO_NONE) + { + exp_seq = m_pSndLossList->popLostSeq(); + have_extracted = (exp_seq != SRT_SEQNO_NONE); + } + else + { + IF_HEAVY_LOGGING(as = "EXPECTED"); + have_extracted = m_pSndLossList->popLostSeq(exp_seq); + } + + HLOGC(gslog.Debug, log << "CUDTGroup::packLostData: " << (have_extracted ? "" : "NOT") << " extracted " + << as << " %" << exp_seq); + + if (have_extracted) + { + w_packet.set_seqno(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.seqno()); + 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(gslog.Error, log << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo " + << w_packet.seqno() << ", 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.seqno(), + CSeqNo::decseq(core->m_iSndLastDataAck) + }; + const int32_t no_msgno = 0; // Message number is not known + + HLOGC(gslog.Debug, log << "PEER reported LOSS not from the sending buffer - requesting DROP: " + << "msg=" << MSGNO_SEQ::unwrap(w_packet.msgflags()) << " SEQ:" + << seqpair[0] << " - " << seqpair[1] << "(" << (-offset) << " packets)"); + + core->sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair)); + return 0; + } + + typedef CSndBuffer::DropRange DropRange; + + DropRange buffer_drop; + steady_clock::time_point origintime; + const int payload = core->m_pSndBuffer->readData(offset, (w_packet), (origintime), (buffer_drop)); + if (payload == CSndBuffer::READ_DROP) + { + SRT_ASSERT(CSeqNo::seqoff(buffer_drop.seqno[DropRange::BEGIN], buffer_drop.seqno[DropRange::END]) >= 0); + + HLOGC(gslog.Debug, + log << CONID() << "loss-reported packets expired in SndBuf - requesting DROP: #" + << buffer_drop.msgno << " %(" << buffer_drop.seqno[DropRange::BEGIN] << " - " + << buffer_drop.seqno[DropRange::END] << ")"); + core->sendCtrl(UMSG_DROPREQ, &buffer_drop.msgno, buffer_drop.seqno, sizeof(buffer_drop.seqno)); + + // skip all dropped packets + m_pSndLossList->removeUpTo(buffer_drop.seqno[DropRange::END]); + core->m_iSndCurrSeqNo = CSeqNo::maxseq(core->m_iSndCurrSeqNo, buffer_drop.seqno[DropRange::END]); + + return 0; + } + else if (payload == CSndBuffer::READ_NONE) + 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.set_msgflags(w_packet.msgflags() | PACKET_SND_REXMIT); + } + + // XXX we don't predict any other use of groups than live, + // so tsbpdmode is always on. Unblock this code otherwise: + // if (!m_bTsbpdMode) + // { + // origintime = steady_clock::now(); + // } + + // Only assert here. Any user-supplied origin time that is earlier + // than start time should be rejected with API error. + SRT_ASSERT(origintime > m_tsStartTime); + + CUDT::setPacketTS(w_packet, m_tsStartTime, origintime); + + return payload; + } + else + { + // This is not the sequence we are looking for. + HLOGC(gslog.Debug, log << "packLostData: expected %" << exp_seq << " not found in the group's loss list"); + } + + return 0; +} + +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())) + { + 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"); + } +} + +// Receiver part + +int CUDTGroup::checkLazySpawnTsbPdThread() +{ + // 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; + + using namespace hvu; + + HLOGP(qrlog.Debug, "Spawning Group TSBPD thread"); +#if HVU_ENABLE_HEAVY_LOGGING + // Take the last 2 ciphers from the socket ID. + string s = fmts(id(), fmtc().fillzero().width(2)); + + const string& tn = fmtcat("SRT:GLat:$", s.substr(s.size()-2, 2)); + + 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_lcc(self->m_RcvDataLock, self->m_RcvDataCond); + CSync tsbpd_cc(self->m_RcvTsbPdCond, recvdata_lcc.locker()); + + self->m_bTsbpdWaitForNewPacket = true; + HLOGC(gmlog.Debug, log << "grp/TSBPD: START"); + while (!self->m_bClosing) + { + enterCS(self->m_RcvBufferLock); + const steady_clock::time_point tnow = steady_clock::now(); + + self->m_pRcvBuffer->updRcvAvgDataSize(tnow); + const srt::CRcvBuffer::PacketInfo info = self->m_pRcvBuffer->getFirstValidPacketInfo(); + + const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); + steady_clock::time_point tsNextDelivery = info.tsbpd_time; + bool rxready = false; + + 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); + + bool synch_loss_after_drop = false; + + 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); + + // 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; + +#if HVU_ENABLE_LOGGING + const int64_t timediff_us = count_microseconds(tnow - info.tsbpd_time); +#endif +#if HVU_ENABLE_HEAVY_LOGGING + HLOGC(tslog.Debug, + 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"); +#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"); + + 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, + 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 + * signal a waiting "recv" call if there is any data available + */ + if (self->m_bSynRecving) + { + 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 + */ + 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. + } + 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); + /* + * Buffer at head of queue is not ready to play. + * Schedule wakeup when it will be. + */ + HLOGC(tslog.Debug, + log << self->CONID() << "grp/tsbpd: FUTURE PACKET seq=" << info.seqno + << " T=" << FormatTime(tsNextDelivery) << " - waiting " << count_milliseconds(timediff) << "ms up to " << FormatTime(tsNextDelivery)); + THREAD_PAUSED(); + got_signal = 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 packet arrived + * - Closing the connection + */ + 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() << "grp/tsbpd: WAKE UP on " << (got_signal ? "signal" : "timeout") + << "; now=" << FormatTime(steady_clock::now())); + } + THREAD_EXIT(); + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: EXITING"); + return NULL; +} + + #if HVU_ENABLE_HEAVY_LOGGING // [[using maybe_locked(CUDT::uglobal()->m_GlobControlLock)]] void CUDTGroup::debugGroup() diff --git a/srtcore/group.h b/srtcore/group.h index 3bc1998d4..e56b4ae32 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; @@ -122,6 +123,8 @@ class CUDTGroup CUDTGroup(SRT_GROUP_TYPE); ~CUDTGroup(); + void createBuffers(int32_t isn, const time_point& tsbpd_start_time, int flow_winsize); + SocketData* add(SocketData data); struct HaveID @@ -148,7 +151,9 @@ class CUDTGroup } // NEED LOCKING + SRT_TSA_NEEDS_LOCKED(m_GroupLock) gli_t begin() { return m_Group.begin(); } + SRT_TSA_NEEDS_LOCKED(m_GroupLock) gli_t end() { return m_Group.end(); } /// Remove the socket from the group container. @@ -169,6 +174,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 @@ -186,7 +192,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(); - resetInitialRxSequence(); + // XXX resetInitialRxSequence() was here, but + // this uses a field that is removed, and replaced by + // m_RcvLastSeqNo empty = true; } } @@ -237,9 +245,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: @@ -349,6 +364,20 @@ 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()); + } + + size_t getAvailBufSize(int32_t last_ack) const + { + srt::sync::ScopedLock lck(m_RcvBufferLock); + return m_pRcvBuffer->getAvailSize(last_ack); + } void close(); @@ -403,7 +432,7 @@ class CUDTGroup /// @param ack The past-the-last-received ACK sequence number void readyPackets(CUDT* core, int32_t ack); - void syncWithSocket(const CUDT& core, const HandshakeSide side); + void syncWithFirstSocket(const CUDT& core, const HandshakeSide side); SRTSTATUS getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize); SRTSTATUS getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize); @@ -422,7 +451,7 @@ class CUDTGroup #endif void ackMessage(int32_t msgno); - void processKeepalive(SocketData*); + void processKeepalive(SocketData*, const CPacket&, const time_point& tsArrival); void internalKeepalive(SocketData*); private: @@ -435,11 +464,13 @@ class CUDTGroup SRTSOCKET m_GroupID; SRTSOCKET m_PeerGroupID; + struct GroupContainer { private: - std::list m_List; - sync::atomic m_SizeCache; + 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 @@ -451,6 +482,7 @@ class CUDTGroup GroupContainer() : m_SizeCache(0) + , m_zNumberRunning(0) , m_LastActiveLink(m_List.end()) { } @@ -470,10 +502,29 @@ 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); }; + + sync::atomic m_zLongestDistance; + atomic_duration m_tdLongestDistance; +public: + SRT_TSA_NEEDS_LOCKED(m_GroupLock) + void updateRcvRunningState(); + + SRT_TSA_NEEDS_LOCKED(m_GroupLock) + void updateErasedLink(); + + void updateInterlinkDistance(); +private: + + SRT_TSA_GUARDED_BY(m_GroupLock) GroupContainer m_Group; SRT_GROUP_TYPE m_type; sync::atomic m_iBusy; @@ -645,6 +696,39 @@ class CUDTGroup // typedef StaticBuffer senderBuffer_t; private: + + UniquePtr m_pSndBuffer; // XXX for future. + + SRT_TSA_PT_GUARDED_BY(m_RcvBufferLock) + UniquePtr m_pRcvBuffer; + + SRT_TSA_PT_GUARDED_BY(m_LossAckLock) + UniquePtr m_pSndLossList; + + 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; + + sync::atomic m_iRcvPossibleLossSeq; + // Fields required for SRT_GTYPE_BACKUP groups. senderBuffer_t m_SenderBuffer; // This mechanism is to be removed on group-common sndbuf int32_t m_iSndOldestMsgNo; // oldest position in the sender buffer @@ -663,14 +747,16 @@ class CUDTGroup bool m_bTsbPd; bool m_bTLPktDrop; int64_t m_iTsbPdDelay_us; - int m_RcvEID; - class CEPollDesc* m_RcvEpolld; int m_SndEID; class CEPollDesc* m_SndEpolld; 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; + bool m_bOPT_DriftTracer; + // 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 @@ -678,21 +764,40 @@ class CUDTGroup time_point m_tsStartTime; time_point m_tsRcvPeerStartTime; - void recv_CollectAliveAndBroken(std::vector& w_alive, std::set& w_broken); + sync::atomic m_RcvLastSeqNo; + sync::AtomicClock m_RcvFurthestPacketTime; + sync::atomic m_SndLastDataAck; - /// 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 - /// - /// @returns list of read-ready sockets - /// @throws CUDTException(MJ_CONNECTION, MN_NOCONN, 0) - /// @throws CUDTException(MJ_AGAIN, MN_RDAVAIL, 0) - std::vector recv_WaitForReadReady(const std::vector& aliveMembers, std::set& w_broken); + // 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. - // 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; + sync::Mutex m_SndLastSeqLock; + + SRT_TSA_GUARDED_BY(m_SndLastSeqLock) + 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; + } + + SRT_TSA_NEEDS_LOCKED(m_SndLastSeqLock) + int32_t getSentSeq() const + { + return m_SndLastSeqNo; + } + +private: /// True: at least one socket has joined the group in at least pending state sync::atomic m_bOpened; @@ -723,6 +828,13 @@ class CUDTGroup /// True: the group was requested to close and it should not allow any operations. sync::atomic 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 @@ -733,10 +845,58 @@ class CUDTGroup // is ready to deliver. 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 + mutable sync::Mutex m_RcvBufferLock; // Protects the state of the m_pRcvBuffer + mutable sync::Mutex m_LossAckLock; + sync::atomic m_iLastSchedSeqNo; // represetnts the value of CUDT::m_iSndNextSeqNo for each running socket sync::atomic m_iLastSchedMsgNo; - // Statistics + // Fields required for balancing groups + 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; + }; + + int configure(const char* str); + 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); + } + + // Statistics struct Stats { // Stats state @@ -841,14 +1001,18 @@ class CUDTGroup #endif } - void resetInitialRxSequence() - { - // 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; - } + 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_TSA_NEEDS_LOCKED(m_RcvBufferLock) + bool checkBalancingLoss(const CPacket& rpkt, CUDT::loss_seqs_t&); + + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) + 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); bool applyGroupTime(time_point& w_start_time, time_point& w_peer_start_time) { @@ -878,18 +1042,27 @@ class CUDTGroup return false; } - // Live state synchronization - bool getBufferTimeBase(CUDT* forthesakeof, time_point& w_tb, bool& w_wp, duration& w_dr); + SRT_TSA_NEEDS_LOCKED(m_GroupLock) 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 CUDT* srcMember); - void updateLatestRcv(CUDTSocket*); void getMemberSockets(std::set&) const; + 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); + void discardSendSchedule(SocketData* d, int ndiscard); + void updateOnACK(int32_t ackdata_seqno); + int packLostData(CUDT* core, CPacket& w_packet, int32_t exp_seq); + + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) + time_point getPktTsbPdTime(uint32_t usPktTimestamp) const + { + return m_pRcvBuffer->getPktTsbPdTime(usPktTimestamp); + } + // Property accessors SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRTSOCKET, id, m_GroupID); SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRTSOCKET, peerid, m_PeerGroupID); @@ -898,6 +1071,9 @@ 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_TSA_NEEDS_LOCKED(m_RcvBufferLock) // RO provides only one getter method + SRTU_PROPERTY_RO(int32_t, getOldestRcvSeqNo, m_pRcvBuffer->getStartSeqNo()); }; } // namespace srt diff --git a/srtcore/group_common.cpp b/srtcore/group_common.cpp index 613057409..824646963 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,11 +53,24 @@ 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 + 0, // rcvSeqDistance + 0, // updateCounter + std::deque() // Could be {}, but in C++11. }; 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 d780d0b9a..25be667ea 100644 --- a/srtcore/group_common.h +++ b/srtcore/group_common.h @@ -16,18 +16,49 @@ 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 - namespace srt { namespace groups { typedef SRT_MEMBERSTATUS GroupState; + enum SeqType + { + /// Freshly first-time to be sent packets. + SQT_FRESH, + + /// Rexmit requests + SQT_LOSS, + + /// 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 + }; + + std::string SeqTypeStr(SeqType); + + 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 @@ -44,14 +75,27 @@ namespace groups bool ready_write; bool ready_error; + // Balancing data + bool use_send_schedule; + 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 + // 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/list.cpp b/srtcore/list.cpp index 713d0717f..c30163c9e 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -95,18 +95,21 @@ void CSndLossList::traceState() const traceState(std::cout) << "\n"; } -int CSndLossList::insert(int32_t seqno1, int32_t seqno2) +int 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; } - const int inserted_range = CSeqNo::seqlen(seqno1, seqno2); + // Make sure that seqhi isn't earlier than seqlo. + SRT_ASSERT(CSeqNo::seqcmp(seqlo, seqhi) <= 0); + + 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; } @@ -114,19 +117,19 @@ int 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; } @@ -134,7 +137,7 @@ int 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) @@ -144,7 +147,7 @@ int 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; } @@ -153,49 +156,53 @@ int CSndLossList::insert(int32_t seqno1, int32_t seqno2) if (offset < 0) { - insertHead(loc, seqno1, seqno2); + HLOGC(qslog.Debug, log << "CSndLossList::insert: offset=" << offset << " - inserting at head"); + 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); + HLOGC(qslog.Debug, log << "CSndLossList::insert: offset=" << offset << " - %" << seqlo << " found at [" << loc << "] - updating"); + 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) + 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; - 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; } @@ -203,7 +210,7 @@ int 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; } @@ -341,6 +348,14 @@ int32_t 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; @@ -360,6 +375,8 @@ int32_t 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; @@ -375,6 +392,238 @@ int32_t 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 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) +{ + SRT_ASSERT(seqlo != SRT_SEQNO_NONE); + + int cmp = CSeqNo::seqcmp(seq, seqlo); + + // 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 may be only seq %> 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); + 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; + int prev_loc = -1; + + // XXX Likely this isn't necessary and the loop can be + // interrupted when this is set to false. + 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); + + 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. + + // 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) + { + 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) + { + // Nice. One single entry. Take the next one + // 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) + { + // 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; + + // 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) + { + // 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; + + 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 + { + // We are in the middle, so the current + // element stays, just gets slashed, and + // the new element has to be created. + + 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; + + // 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; + } + + return false; +} + void CSndLossList::insertHead(int pos, int32_t seqno1, int32_t seqno2) { SRT_ASSERT(pos >= 0); diff --git a/srtcore/list.h b/srtcore/list.h index bf9410a95..917bc3374 100644 --- a/srtcore/list.h +++ b/srtcore/list.h @@ -75,13 +75,18 @@ 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; /// 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); template Stream& traceState(Stream& sout) const @@ -144,6 +149,8 @@ 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: @@ -281,7 +288,7 @@ struct CRcvFreshLoss int ttl; 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! diff --git a/srtcore/packetfilter.h b/srtcore/packetfilter.h index f7e5efc1b..dbcb2bd58 100644 --- a/srtcore/packetfilter.h +++ b/srtcore/packetfilter.h @@ -116,7 +116,7 @@ class PacketFilter SrtPacketFilterBase* m_filter; void Check() { -#if ENABLE_DEBUG +#if SRT_ENABLE_DEBUG if (!m_filter) abort(); #endif diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index df7be7eee..525947b77 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -70,10 +70,11 @@ using namespace hvu; // ThreadName namespace srt { -CUnitQueue::CUnitQueue(int initNumUnits, int mss) +CUnitQueue::CUnitQueue(int initNumUnits, int mss, SRTSOCKET owner) : m_iNumTaken(0) , m_iMSS(mss) , m_iBlockSize(initNumUnits) + , m_OwnerID(owner) { CQEntry* tempq = allocateEntry(m_iBlockSize, m_iMSS); @@ -131,6 +132,7 @@ CUnitQueue::CQEntry* CUnitQueue::allocateEntry(const int iNumUnits, const int ms for (int i = 0; i < iNumUnits; ++i) { tempu[i].m_bTaken = false; + tempu[i].m_pParentQueue = this; tempu[i].m_Packet.m_pcData = tempb + i * mss; } @@ -527,10 +529,10 @@ void CSndQueue::workerSendOrder() CPacket pkt; steady_clock::time_point next_send_time; CNetworkInterface source_addr; - const bool res = u.packData((pkt), (next_send_time), (source_addr)); + const bool valid = u.packData((pkt), (next_send_time), (source_addr)); // Check if extracted anything to send - if (res == false) + if (!valid) { HLOGC(qslog.Debug, log << "packData: nothing to send, WITHDRAWING sender"); sched.remove(runner); @@ -969,7 +971,9 @@ void CMultiplexer::configure(int32_t id, const CSrtConfig& config, const sockadd // (Likely here configure the hash table for m_Sockets). HLOGC(smlog.Debug, log << "@" << id << ": configureMuxer: config rcv queue qsize=" << 128 << " plsize=" << payload_size << " hsize=" << 1024); - m_RcvQueue.init(128, payload_size, m_pChannel); + + // XXX This will have to be changed for the new receiver buffer unit. + m_RcvQueue.init(128, payload_size, m_pChannel, id); } void CMultiplexer::removeSender(CUDT* u) @@ -1046,12 +1050,12 @@ void srt::CRcvQueue::resetAtFork() sync::atomic CRcvQueue::m_counter(0); #endif -void CRcvQueue::init(int qsize, size_t payload, CChannel* cc) +void CRcvQueue::init(int qsize, size_t payload, CChannel* cc, SRTSOCKET owner) { m_szPayloadSize = payload; SRT_ASSERT(m_pUnitQueue == NULL); - m_pUnitQueue = new CUnitQueue(qsize, (int)payload); + m_pUnitQueue = new CUnitQueue(qsize, (int)payload, owner); m_pChannel = cc; @@ -1225,6 +1229,7 @@ EReadStatus CRcvQueue::worker_RetrieveUnit(SRTSOCKET& w_id, CUnit*& w_unit, sock w_id = w_unit->m_Packet.id(); 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; @@ -1400,6 +1405,20 @@ EConnectStatus CRcvQueue::worker_ProcessAddressedPacket(SRTSOCKET id, CUnit* uni else u->processData(unit); + // NOTE: handling in the above functions MIGHT also turn it into broken. + // If so, DO NOT do checkTimers(). + if (!u->m_bConnected || u->m_bBroken || u->m_bClosing) + { + if (u->m_RejectReason == SRT_REJ_UNKNOWN) + u->m_RejectReason = SRT_REJ_CLOSE; + + HLOGC(cnlog.Debug, log << "worker_ProcessAddressedPacket: target @" + << id << " was closed in the handler, NOT UPDATING"); + + // This time return CONN_RUNNING because the packet was handled. + return CONN_RUNNING; + } + HLOGC(cnlog.Debug, log << "POST-DISPATCH update for @" << id); u->checkTimers(); diff --git a/srtcore/queue.h b/srtcore/queue.h index 3f8cc3663..cc851f912 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -68,10 +68,13 @@ namespace srt class CChannel; class CUDT; +class CUnitQueue; + struct CUnit { CPacket m_Packet; // packet sync::atomic m_bTaken; // true if the unit is is use (can be stored in the RCV buffer). + CUnitQueue* m_pParentQueue; }; class CUnitQueue @@ -81,13 +84,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 @@ -118,7 +123,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 @@ -129,6 +134,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&); @@ -484,7 +490,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, CChannel* c); + void init(int size, size_t payload, CChannel* c, SRTSOCKET owner); /// Read a packet for a specific UDT socket id. /// @param [in] id Socket ID diff --git a/srtcore/srt.h b/srtcore/srt.h index 75ae2aa7c..6a68b0742 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -255,13 +255,14 @@ typedef enum SRT_SOCKOPT { SRTO_IPV6ONLY, // IPV6_V6ONLY mode SRTO_PEERIDLETIMEO, // Peer-idle timeout (max time of silence heard from peer) in [ms] SRTO_BINDTODEVICE, // Forward the SOL_SOCKET/SO_BINDTODEVICE option on socket (pass packets only from that device) - SRTO_GROUPCONNECT, // Set on a listener to allow group connection (ENABLE_BONDING) - SRTO_GROUPMINSTABLETIMEO, // Minimum Link Stability timeout (backup mode) in milliseconds (ENABLE_BONDING) - SRTO_GROUPTYPE, // Group type to which an accepted socket is about to be added, available in the handshake (ENABLE_BONDING) + SRTO_GROUPCONNECT, // Set on a listener to allow group connection (SRT_ENABLE_BONDING) + SRTO_GROUPMINSTABLETIMEO, // Minimum Link Stability timeout (backup mode) in milliseconds (SRT_ENABLE_BONDING) + SRTO_GROUPTYPE, // Group type to which an accepted socket is about to be added, available in the handshake (SRT_ENABLE_BONDING) SRTO_PACKETFILTER = 60, // Add and configure a packet filter SRTO_RETRANSMITALGO = 61, // An option to select packet retransmission algorithm SRTO_CRYPTOMODE = 62, // Encryption cipher mode (AES-CTR, AES-GCM, ...). SRTO_MAXREXMITBW = 63, // Maximum bandwidth limit for retransmision (Bytes/s) + SRTO_GROUPCONFIG = 70, SRTO_E_SIZE // Always last element, not a valid option. } SRT_SOCKOPT; @@ -953,13 +954,14 @@ SRT_API int64_t srt_connection_time(SRTSOCKET sock); SRT_API int srt_clock_type(void); -// SRT Socket Groups API (ENABLE_BONDING) +// SRT Socket Groups API (SRT_ENABLE_BONDING) 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/utilities.h b/srtcore/utilities.h index 174c867ce..4c2378e4f 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -120,8 +120,7 @@ struct BitsetMask static const uint32_t value = (1u << L) | BitsetMask::value; }; -// This is kind-of functional programming. This describes a special case that is -// a "terminal case" in case when decreased L-1 (see above) reached == R. +// A "terminal case" in case when decreased L-1 (see above) reached == R. template struct BitsetMask { @@ -129,8 +128,7 @@ struct BitsetMask static const uint32_t value = 1u << R; }; -// This is a trap for a case that BitsetMask::correct in the master template definition -// evaluates to false - stops infinite template instantiation recursion with error. +// A trap for mis-specified L and R (when L < R) template struct BitsetMask { @@ -156,15 +154,6 @@ struct Bits }; -//inline int32_t Bit(size_t b) { return 1 << b; } -// XXX This would work only with 'constexpr', but this is -// available only in C++11. In C++03 this can be only done -// using a macro. -// -// Actually this can be expressed in C++11 using a better technique, -// such as user-defined literals: -// 2_bit --> 1 >> 2 - #ifdef BIT #undef BIT #endif @@ -299,11 +288,7 @@ class HeapSet return Access::key(m_HeapArray[position]); } - // Retuirn the value to compare as "no element" - static NodeType none() - { - return Access::none(); - } + static NodeType none() { return Access::none(); } // Provide the "npos" value to define a position value for // a node that is not in the heap. @@ -388,19 +373,15 @@ class HeapSet // a trap value because the first 3 items are checked on a fast path). size_t find_next_candidate(size_t position, typename Access::key_type limit) const { - // It should be guaranteed before the call that position is still - // within the range of existing elements. + // NOTE: `position` is unchecked. - // Ok, so first you check the element at position. If this element - // is already the next after limit, return it. + // If this element is already the next after limit, return it. if (!Access::order(keyat(position), limit)) return position; // Otherwise check the children and if both are next to it, select the - // earlier one in order. - - // If both children are prior to limit, call this function for both - // children and select the next one. + // earlier one in order. If both children are prior to limit, call + // this function for both children and select the next one. size_t left_pos = left(position), right_pos = right(position); @@ -455,7 +436,6 @@ class HeapSet // SINCE THIS LINE ON: // Both left_check and right_check can be either 1 or -1. - // But potentially can have only left == -1. if (left_check == -1) @@ -529,14 +509,7 @@ class HeapSet return last; } - // Returns the minimum key (key at root) from min heap - // This function is UNCHECKED. Call it only if you are - // certain that the heap contains at least one element. - NodeType top_raw() - { - return m_HeapArray[0]; - } - + NodeType top_raw() { return m_HeapArray[0]; } NodeType top() { if (m_HeapArray.empty()) @@ -544,16 +517,12 @@ class HeapSet return top_raw(); } - // Convenience wrapper to insert the node at the new key. - // You can still assign the key first yourself and then request to insert it, - // but this serves better as map-like insert. size_t insert(const typename Access::key_type& key, NodeType node) { Access::key(node) = key; return insert(node); } - // Inserts a new key 'k' size_t insert(NodeType node) { // First insert the new key at the end @@ -624,7 +593,7 @@ class HeapSet size_t r = right(i); size_t earliest = i; -#if 0 // ENABLE_LOGGING +#if 0 // HVU_ENABLE_LOGGING std::string which = "parent"; // LOGN("REHEAP: [", i, "]", Access::print(m_HeapArray[i]), " -> "); if (l < m_HeapArray.size()) @@ -723,8 +692,7 @@ class HeapSet }; -// std::addressof in C++11, -// needs to be provided for C++03 +// std::addressof in C++11, needs to be provided for C++03 template inline RefType* AddressOf(RefType& r) { @@ -1017,10 +985,6 @@ inline pair_proxy Tie(Type1& var1, Type2& var2) return pair_proxy(var1, var2); } -// This can be used in conjunction with Tie to simplify the code -// in loops around a whole container: -// list::const_iterator it, end; -// Tie(it, end) = All(list_container); template inline std::pair All(Container& c) { return std::make_pair(c.begin(), c.end()); } diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index bff3f3618..dba220439 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -62,7 +62,7 @@ TEST(Bonding, SRTConnectGroup) } } -#define EXPECT_SRT_SUCCESS(callform) EXPECT_NE(callform, -1) << "SRT ERROR: " << srt_getlasterror_str() +#define EXPECT_SRT_SUCCESS(callform) EXPECT_NE(callform, int(SRT_ERROR)) << "SRT ERROR: " << srt_getlasterror_str() static std::mutex g_listening_stopped; @@ -87,7 +87,7 @@ void listening_thread(bool should_read) EXPECT_SRT_SUCCESS(srt_epoll_add_usock(eid, server_sock, &listen_event)); EXPECT_SRT_SUCCESS(srt_listen(server_sock, 5)); - std::cout << "Listen: wait for acceptability\n"; + std::cout << "Listen: @" << server_sock << " - wait for acceptability\n"; int fds[2]; int fds_len = 2; int ers[2]; @@ -104,6 +104,21 @@ void listening_thread(bool should_read) EXPECT_SRT_SUCCESS(acp); EXPECT_NE(acp & SRTGROUP_MASK, 0); + SRT_SOCKGROUPDATA gd[4]; + + using namespace std::chrono; + + std::this_thread::sleep_for(200ms); + + size_t gd_size = 4; + int gd_stat = srt_group_data(acp, (gd), (&gd_size)); + EXPECT_NE(gd_stat, -1); + + std::cout << "Listen: accepted $" << acp << " Members: "; + for (size_t i = 0; i < gd_size; ++i) + std::cout << "@" << gd[i].id << " "; + std::cout << std::endl; + if (should_read) { std::cout << "Listener will read packets...\n"; @@ -125,12 +140,12 @@ void listening_thread(bool should_read) std::cout << "Listen: wait for green light from the caller...\n"; std::unique_lock listen_lock (g_listening_stopped); + std::cout << "Listen: CLOSING accepted $" << acp << " and self @" << server_sock << std::endl; srt_close(acp); srt_close(server_sock); std::cout << "Listen: wait 7 seconds\n"; std::this_thread::sleep_for(std::chrono::seconds(7)); - // srt_accept.. } SRTSOCKET g_listen_socket = -1; @@ -170,7 +185,7 @@ TEST(Bonding, NonBlockingGroupConnect) const int ss = srt_create_group(SRT_GTYPE_BROADCAST); ASSERT_NE(ss, SRT_ERROR); - std::cout << "Created group socket: " << ss << '\n'; + std::cout << "Created group: $" << ss << '\n'; int no = 0; EXPECT_NE(srt_setsockopt(ss, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode @@ -259,7 +274,7 @@ void ConnectCallback_Close(void* /*opaq*/, SRTSOCKET sock, int error, const sock if (error == SRT_SUCCESS) return; - // XXX WILL CAUSE DEADLOCK! + std::cout << "CLOSING @" << sock << ": -- IGNORE BELOW ERROR LOG (you should not close the socket from callback)\n"; srt_close(sock); } @@ -288,7 +303,7 @@ TEST(Bonding, CloseGroupAndSocket) ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); std::future listen_promise = std::async(std::launch::async, std::bind(listening_thread, true)); - + std::cout << "Connecting two sockets " << std::endl; for (int i = 0; i < 2; ++i) { @@ -345,7 +360,7 @@ TEST(Bonding, CloseGroupAndSocket) EXPECT_EQ(stats.pktRcvDropTotal, 0); std::cout << "Starting thread for sending:\n"; - std::thread sender([ss] { + std::thread sender([&ss] { char buf[1316]; memset(buf, 1, sizeof(buf)); int n = 0; diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp index 03310c8d4..d98f49fc2 100644 --- a/test/test_buffer_rcv.cpp +++ b/test/test_buffer_rcv.cpp @@ -26,12 +26,12 @@ class CRcvBufferReadMsg void SetUp() override { // make_unique is unfortunately 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); const bool enable_msg_api = m_use_message_api; const bool enable_peer_rexmit = true; - m_rcv_buffer.reset(new CRcvBuffer(m_init_seqno, m_buff_size_pkts, m_unit_queue.get(), enable_msg_api)); + m_rcv_buffer.reset(new CRcvBuffer(m_init_seqno, m_buff_size_pkts, enable_msg_api)); m_rcv_buffer->setPeerRexmitFlag(enable_peer_rexmit); ASSERT_NE(m_rcv_buffer.get(), nullptr); } @@ -49,6 +49,12 @@ class CRcvBufferReadMsg /// /// @returns the result of rcv_buffer::insert(..) int addPacket(int seqno, int msgno, bool pb_first = true, bool pb_last = true, bool out_of_order = false, int ts = 0) + { + PacketBoundary bound = PacketBoundary((pb_first * PB_FIRST) | (pb_last * PB_LAST)); + return addPacket(seqno, msgno, bound, out_of_order, ts); + } + + int addPacket(int32_t seqno, int msgno, PacketBoundary bound, bool out_of_order = false, int ts = 0) { CUnit* unit = m_unit_queue->getNextAvailUnit(); EXPECT_NE(unit, nullptr); @@ -60,15 +66,7 @@ class CRcvBufferReadMsg packet.setLength(m_payload_sz); generatePayload(packet.data(), packet.getLength(), packet.seqno()); - int32_t pktMsgFlags = msgno; - pktMsgFlags |= PacketBoundaryBits(PB_SUBSEQUENT); - if (pb_first) - pktMsgFlags |= PacketBoundaryBits(PB_FIRST); - if (pb_last) - pktMsgFlags |= PacketBoundaryBits(PB_LAST); - - if (!out_of_order) - pktMsgFlags |= MSGNO_PACKET_INORDER::wrap(1); + int32_t pktMsgFlags = msgno | PacketBoundaryBits(bound) | MSGNO_PACKET_INORDER::wrap(!out_of_order); packet.set_msgflags(pktMsgFlags); if (!out_of_order) @@ -124,7 +122,8 @@ class CRcvBufferReadMsg int readMessage(char* data, size_t len) { - return m_rcv_buffer->readMessage(data, len); + SRT_MSGCTRL dummy; + return m_rcv_buffer->readMessage(data, len, (dummy)); } bool hasAvailablePackets() @@ -623,7 +622,8 @@ TEST_F(CRcvBufferReadMsg, MsgOutOfOrderDrop) // 2. Read full message after gap. const size_t msg_bytelen = msg_pkts * m_payload_sz; array buff; - int res = m_rcv_buffer->readMessage(buff.data(), buff.size()); + SRT_MSGCTRL mc; + int res = m_rcv_buffer->readMessage(buff.data(), buff.size(), (mc)); EXPECT_EQ(res, (int) msg_bytelen); for (size_t i = 0; i < msg_pkts; ++i) { @@ -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*5); + 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*3); + 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)); @@ -731,10 +731,11 @@ TEST_F(CRcvBufferReadMsg, MsgOutOfOrderAfterInOrder) // 2. Read messages in order const size_t msg_bytelen = msg_pkts * m_payload_sz; std::array buff; + SRT_MSGCTRL mc; for (int msg_i = 0; msg_i < 3; ++msg_i) { EXPECT_TRUE(m_rcv_buffer->isRcvDataReady()); - EXPECT_EQ(m_rcv_buffer->readMessage(buff.data(), buff.size()), (int) msg_bytelen); + EXPECT_EQ(m_rcv_buffer->readMessage(buff.data(), buff.size(), (mc)), (int) msg_bytelen); for (size_t i = 0; i < msg_pkts; ++i) { EXPECT_TRUE(verifyPayload(buff.data() + i * m_payload_sz, m_payload_sz, int(m_init_seqno + msg_i * msg_pkts + i))); @@ -779,7 +780,8 @@ TEST_F(CRcvBufferReadMsg, OnePacketTSBPD) EXPECT_TRUE(m_rcv_buffer->isRcvDataReady(m_tsbpd_base + m_delay + sync::microseconds_from(1))); // Read out the first message - const int read_len = m_rcv_buffer->readMessage(buff.data(), buff.size()); + SRT_MSGCTRL mc; + const int read_len = m_rcv_buffer->readMessage(buff.data(), buff.size(), (mc)); EXPECT_EQ(read_len, (int) msg_bytelen); EXPECT_TRUE(verifyPayload(buff.data(), read_len, m_init_seqno)); diff --git a/test/test_enforced_encryption.cpp b/test/test_enforced_encryption.cpp index eff56031b..55339f1e1 100644 --- a/test/test_enforced_encryption.cpp +++ b/test/test_enforced_encryption.cpp @@ -614,12 +614,13 @@ class TestEnforcedEncryption char buffer[1316] = {1, 2, 3, 4}; ASSERT_NE(srt_sendmsg2(m_caller_socket, buffer, sizeof buffer, nullptr), int(SRT_ERROR)); std::this_thread::sleep_for(std::chrono::seconds(1)); + srt_epoll_release(epollWrite); } SRTSOCKET srtSocket = SRT_INVALID_SOCK; int socketNum = 1; int epoll_res_r = srt_epoll_wait(epollRead, &srtSocket, &socketNum, nullptr, nullptr, 500, nullptr, nullptr, nullptr, nullptr); - fout.puts(" ... R: ", epoll_res_r); + fout.puts(" ... R:", epoll_res_r); EXPECT_LE(epoll_res_r, 0) << "It's wrongly reported, so let's take a look..."; char buffer[1316] = {}; EXPECT_EQ(srt_recvmsg2(accepted_socket, buffer, sizeof buffer, nullptr), -1); diff --git a/test/test_losslist_snd.cpp b/test/test_losslist_snd.cpp index 5ff3e7744..59a2e832a 100644 --- a/test/test_losslist_snd.cpp +++ b/test/test_losslist_snd.cpp @@ -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/test/test_unitqueue.cpp b/test/test_unitqueue.cpp index c58242727..fce9f8697 100644 --- a/test/test_unitqueue.cpp +++ b/test/test_unitqueue.cpp @@ -18,7 +18,7 @@ TEST(CUnitQueue, Increase) { srt::TestInit srtinit; 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) @@ -39,7 +39,7 @@ TEST(CUnitQueue, IncreaseAndFree) { srt::TestInit srtinit; 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) @@ -64,7 +64,7 @@ TEST(CUnitQueue, IncreaseAndFreeGrouped) { srt::TestInit srtinit; 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) diff --git a/testing/testmedia.cpp b/testing/testmedia.cpp index 3132050d6..6727d6e1a 100755 --- a/testing/testmedia.cpp +++ b/testing/testmedia.cpp @@ -1085,7 +1085,8 @@ SRT_GROUP_TYPE ResolveGroupType(const string& name) } table [] { #define E(n) {#n, SRT_GTYPE_##n} E(BROADCAST), - E(BACKUP) + E(BACKUP), + E(BALANCING) #undef E };