diff --git a/association.go b/association.go index 4f9658d7..737458d1 100644 --- a/association.go +++ b/association.go @@ -1969,11 +1969,11 @@ func (a *Association) processSelectiveAck(selectiveAckChunk *chunkSelectiveAck) newestDeliveredOrigTSN = chunkPayload.tsn deliveredFound = true } + } - if a.inFastRecovery && chunkPayload.tsn == a.fastRecoverExitPoint { - a.log.Debugf("[%s] exit fast-recovery", a.name) - a.inFastRecovery = false - } + if a.inFastRecovery && chunkPayload.tsn == a.fastRecoverExitPoint { + a.log.Debugf("[%s] exit fast-recovery", a.name) + a.inFastRecovery = false } } diff --git a/association_test.go b/association_test.go index fe196599..cbfdb418 100644 --- a/association_test.go +++ b/association_test.go @@ -4326,6 +4326,28 @@ func TestRACK_PTO_DoesNotProbe_WhenPendingExists(t *testing.T) { assert.False(t, got.retransmit, "PTO must prefer sending pending data over probing") } +func TestFastRecoveryExitOnAckedExitPoint(t *testing.T) { + assoc := newRackTestAssoc(t) + + now := time.Now() + assoc.inflightQueue.pushNoCheck(mkChunk(100, now.Add(-20*time.Millisecond))) + assoc.inflightQueue.pushNoCheck(mkChunk(101, now.Add(-10*time.Millisecond))) + + assoc.inflightQueue.markAsAcked(101) + assoc.inFastRecovery = true + assoc.fastRecoverExitPoint = 101 + + assoc.lock.Lock() + _, _, _, _, _, err := assoc.processSelectiveAck(&chunkSelectiveAck{ //nolint:dogsled + cumulativeTSNAck: 101, + }) + exited := !assoc.inFastRecovery + assoc.lock.Unlock() + + require.NoError(t, err) + assert.True(t, exited, "fast recovery should exit when exit point is cumulatively acked") +} + func TestRTOClearsFastRecovery(t *testing.T) { assoc := newRackTestAssoc(t)