@@ -229,15 +229,11 @@ type Association struct {
229229 ackTimer * ackTimer
230230
231231 // RACK / TLP state
232- rackEnabled bool
233- rackReoWnd time.Duration // dynamic reordering window
234- rackMinRTT time.Duration // min observed RTT
235- rackMinRTTWnd time.Duration // the window used to determine minRTT, defaults to 30s
236- rackRTTDeque []struct { // sliding window of RTT samples within rackMinRTTWnd seconds
237- time time.Time
238- rtt time.Duration
239- }
240- rackDeliveredTime time.Time // send time of most recently delivered original chunk
232+ rackEnabled bool
233+ rackReoWnd time.Duration // dynamic reordering window
234+ rackMinRTT time.Duration // min observed RTT
235+ rackMinRTTWnd * windowedMin // the window used to determine minRTT, defaults to 30s
236+ rackDeliveredTime time.Time // send time of most recently delivered original chunk
241237 rackHighestDeliveredOrigTSN uint32
242238 rackReorderingSeen bool // ever observed reordering for this association
243239 rackKeepInflatedRecoveries int // keep inflated reoWnd for 16 loss recoveries
@@ -436,11 +432,8 @@ func createAssociation(config Config) *Association {
436432 assoc .rackWCDelAck = 200 * time .Millisecond // WCDelAckT, RACK for SCTP section 2C
437433 }
438434
439- if config .RACKMinRTTWnd != 0 {
440- assoc .rackMinRTTWnd = config .RACKMinRTTWnd
441- } else {
442- assoc .rackMinRTTWnd = 30 * time .Second // default 30s window to determine minRTT
443- }
435+ // defaults to 30s window to determine minRTT
436+ assoc .rackMinRTTWnd = newWindowedMin (config .RACKMinRTTWnd )
444437
445438 assoc .timerUpdateCh = make (chan struct {}, 1 )
446439 go assoc .timerLoop ()
@@ -1791,7 +1784,7 @@ func (a *Association) processSelectiveAck(selectiveAckChunk *chunkSelectiveAck)
17911784 // use a window to determine minRtt instead of a global min
17921785 // as the RTT can fluctuate, which can cause problems if going from a
17931786 // high RTT to a low RTT.
1794- a .rackPushRTT (now , now .Sub (chunkPayload .since ))
1787+ a .rackMinRTTWnd . Push (now , now .Sub (chunkPayload .since ))
17951788
17961789 if chunkPayload .since .After (newestDeliveredSendTime ) {
17971790 newestDeliveredSendTime = chunkPayload .since
@@ -1842,7 +1835,7 @@ func (a *Association) processSelectiveAck(selectiveAckChunk *chunkSelectiveAck)
18421835
18431836 // RACK
18441837 if a .rackEnabled {
1845- a .rackPushRTT (now , now .Sub (chunkPayload .since ))
1838+ a .rackMinRTTWnd . Push (now , now .Sub (chunkPayload .since ))
18461839
18471840 if chunkPayload .since .After (newestDeliveredSendTime ) {
18481841 newestDeliveredSendTime = chunkPayload .since
@@ -3065,11 +3058,7 @@ func (a *Association) startRackTimer(dur time.Duration) {
30653058
30663059 a .timerMu .Unlock ()
30673060
3068- // poke the loop
3069- select {
3070- case a .timerUpdateCh <- struct {}{}:
3071- default :
3072- }
3061+ a .pokeTimerLoop ()
30733062}
30743063
30753064func (a * Association ) stopRackTimer () {
@@ -3081,10 +3070,7 @@ func (a *Association) stopRackTimer() {
30813070 a .rackDeadline = time.Time {}
30823071 a .timerMu .Unlock ()
30833072
3084- select {
3085- case a .timerUpdateCh <- struct {}{}:
3086- default :
3087- }
3073+ a .pokeTimerLoop ()
30883074}
30893075
30903076func (a * Association ) startPTOTimer (dur time.Duration ) {
@@ -3102,11 +3088,7 @@ func (a *Association) startPTOTimer(dur time.Duration) {
31023088
31033089 a .timerMu .Unlock ()
31043090
3105- // poke the loop
3106- select {
3107- case a .timerUpdateCh <- struct {}{}:
3108- default :
3109- }
3091+ a .pokeTimerLoop ()
31103092}
31113093
31123094func (a * Association ) stopPTOTimer () {
@@ -3118,10 +3100,7 @@ func (a *Association) stopPTOTimer() {
31183100 a .ptoDeadline = time.Time {}
31193101 a .timerMu .Unlock ()
31203102
3121- select {
3122- case a .timerUpdateCh <- struct {}{}:
3123- default :
3124- }
3103+ a .pokeTimerLoop ()
31253104}
31263105
31273106// drainTimer safely stops a timer and drains its channel if needed.
@@ -3250,7 +3229,7 @@ func (a *Association) onRackAfterSACK( // nolint:gocognit,cyclop,gocyclo
32503229 }
32513230
32523231 // 2) Maintain ReoWND (RACK for SCTP section 2B)
3253- if minRTT := a .rackWindowMin (now ); minRTT > 0 {
3232+ if minRTT := a .rackMinRTTWnd . Min (now ); minRTT > 0 {
32543233 a .rackMinRTT = minRTT
32553234 }
32563235
@@ -3442,53 +3421,10 @@ func (a *Association) onPTOTimerLocked() {
34423421 }
34433422}
34443423
3445- // push a new RTT sample and keep a deque within [now - rackMinRTTWnd, now].
3446- func (a * Association ) rackPushRTT (now time.Time , rtt time.Duration ) {
3447- cutoff := now .Add (- a .rackMinRTTWnd )
3448-
3449- // remove the expired rtts
3450- deque := a .rackRTTDeque
3451- i := 0
3452- for i < len (deque ) && deque [i ].time .Before (cutoff ) {
3453- i ++
3454- }
3455-
3456- if i > 0 {
3457- deque = deque [i :]
3458- }
3459-
3460- // drop tails with >= rtt to keep nondecreasing by rtt
3461- for len (deque ) > 0 && deque [len (deque )- 1 ].rtt >= rtt {
3462- deque = deque [:len (deque )- 1 ]
3463- }
3464-
3465- deque = append (deque , struct {
3466- time time.Time
3467- rtt time.Duration
3468- }{time : now , rtt : rtt })
3469-
3470- a .rackRTTDeque = deque
3471- }
3472-
3473- // return current windowed MinRTT (or 0 if no recent samples).
3474- func (a * Association ) rackWindowMin (now time.Time ) time.Duration {
3475- cutoff := now .Add (- a .rackMinRTTWnd )
3476- deque := a .rackRTTDeque
3477-
3478- // prune expired
3479- i := 0
3480- for i < len (deque ) && deque [i ].time .Before (cutoff ) {
3481- i ++
3482- }
3483-
3484- if i > 0 {
3485- deque = deque [i :]
3486- a .rackRTTDeque = deque
3487- }
3488-
3489- if len (deque ) == 0 {
3490- return 0
3424+ func (a * Association ) pokeTimerLoop () {
3425+ // enqueue a single wake-up without blocking.
3426+ select {
3427+ case a .timerUpdateCh <- struct {}{}:
3428+ default :
34913429 }
3492-
3493- return deque [0 ].rtt
34943430}
0 commit comments