detached cfccd91

improve(dgram): add cumulative ACK RTT resend

commit
cfccd91258fae113d35cfd3fae42cf853709c78a
author
brice · · 2026-07-09 16:23 UTC
parent
a95d4db
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +163 -16
M internal/adapters/dgram/transport.go +73 -14
@@ -144,6 +144,9 @@ type Transport struct {
144144 resendAfter time.Duration
145145 maxResendAfter time.Duration
146146 maxResendPerTick int
147+ srtt time.Duration
148+ rttvar time.Duration
149+ rto time.Duration
147150 writeTimeout time.Duration
148151 degradedAfter time.Duration
149152 probeAfter time.Duration
@@ -176,10 +179,11 @@ type Transport struct {
176179 }
177180
178181 type pending struct {
179- frame ports.Frame
180- first time.Time
181- last time.Time
182- attempts int
182+ frame ports.Frame
183+ first time.Time
184+ last time.Time
185+ attempts int
186+ retransmitted bool
183187 }
184188
185189 func NewTransport(pc net.PacketConn, peer net.Addr, key []byte, sendDir, recvDir uint32) (*Transport, error) {
@@ -209,6 +213,7 @@ func NewTransportWithOptions(pc net.PacketConn, peer net.Addr, key []byte, sendD
209213 resendAfter: opts.ResendAfter,
210214 maxResendAfter: opts.MaxResendAfter,
211215 maxResendPerTick: opts.MaxResendPerTick,
216+ rto: opts.ResendAfter,
212217 writeTimeout: opts.WriteTimeout,
213218 degradedAfter: opts.DegradedAfter,
214219 probeAfter: opts.ProbeAfter,
@@ -499,8 +504,18 @@ func (t *Transport) handleRecord(p []byte) {
499504 }
500505 seq := binary.BigEndian.Uint64(p[1:])
501506 t.mu.Lock()
502- if _, ok := t.pending[seq]; ok {
503- delete(t.pending, seq)
507+ acked := false
508+ for pendingSeq, p := range t.pending {
509+ if pendingSeq > seq {
510+ continue
511+ }
512+ if pendingSeq == seq && p != nil && !p.retransmitted {
513+ t.updateRTTLocked(t.clock.Now().Sub(p.first))
514+ }
515+ delete(t.pending, pendingSeq)
516+ acked = true
517+ }
518+ if acked {
504519 t.notifySendWaitersLocked()
505520 }
506521 t.mu.Unlock()
@@ -530,9 +545,9 @@ func (t *Transport) handleRecord(p []byte) {
530545 return
531546 }
532547 if reliable {
533- ack, queued := t.enqueueReliable(seq, f)
548+ ackSeq, ack, queued := t.enqueueReliable(seq, f)
534549 if ack {
535- t.sendAck(seq)
550+ t.sendAck(ackSeq)
536551 }
537552 if queued {
538553 t.signalDelivery()
@@ -543,20 +558,31 @@ func (t *Transport) handleRecord(p []byte) {
543558 }
544559 }
545560
546-func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ack bool, queued bool) {
561+func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ackSeq uint64, ack bool, queued bool) {
547562 t.deliverMu.Lock()
548563 defer t.deliverMu.Unlock()
549564 if seq < t.nextRecvSeq {
550- return true, false
565+ return t.highestContiguousRecvLocked(), true, false
551566 }
552567 if _, exists := t.recvBuf[seq]; exists {
553- return true, false
568+ return t.highestContiguousRecvLocked(), true, false
554569 }
555570 if len(t.recvBuf) >= t.maxRecvBuffer && seq != t.nextRecvSeq {
556- return false, false
571+ return 0, false, false
557572 }
558573 t.recvBuf[seq] = f
559- return true, true
574+ return t.highestContiguousRecvLocked(), true, true
575+}
576+
577+func (t *Transport) highestContiguousRecvLocked() uint64 {
578+ seq := t.nextRecvSeq
579+ for {
580+ if _, ok := t.recvBuf[seq]; !ok {
581+ break
582+ }
583+ seq++
584+ }
585+ return seq - 1
560586 }
561587
562588 func (t *Transport) signalDelivery() {
@@ -662,6 +688,7 @@ func (t *Transport) resendPending() {
662688 }
663689 p.last = now
664690 p.attempts++
691+ p.retransmitted = true
665692 resend = append(resend, struct {
666693 seq uint64
667694 f ports.Frame
@@ -675,7 +702,10 @@ func (t *Transport) resendPending() {
675702 }
676703
677704 func (t *Transport) resendDelayLocked(p *pending) time.Duration {
678- d := t.resendAfter
705+ d := t.rto
706+ if d <= 0 {
707+ d = t.resendAfter
708+ }
679709 for i := 0; i < p.attempts && d < t.maxResendAfter; i++ {
680710 d *= 2
681711 if d > t.maxResendAfter {
@@ -685,6 +715,35 @@ func (t *Transport) resendDelayLocked(p *pending) time.Duration {
685715 return d
686716 }
687717
718+func (t *Transport) updateRTTLocked(sample time.Duration) {
719+ if sample <= 0 {
720+ return
721+ }
722+ if t.srtt <= 0 {
723+ t.srtt = sample
724+ t.rttvar = sample / 2
725+ } else {
726+ delta := t.srtt - sample
727+ if delta < 0 {
728+ delta = -delta
729+ }
730+ t.rttvar = (3*t.rttvar + delta) / 4
731+ t.srtt = (7*t.srtt + sample) / 8
732+ }
733+ variation := 4 * t.rttvar
734+ if variation < time.Millisecond {
735+ variation = time.Millisecond
736+ }
737+ rto := t.srtt + variation
738+ if rto < t.resendAfter {
739+ rto = t.resendAfter
740+ }
741+ if rto > t.maxResendAfter {
742+ rto = t.maxResendAfter
743+ }
744+ t.rto = rto
745+}
746+
688747 func (t *Transport) hopPacketConnOnce() {
689748 t.mu.Lock()
690749 if t.rebind == nil || t.hoppedOffline || t.closed {
@@ -144,6 +144,9 @@ type Transport struct {
144 resendAfter time.Duration144 resendAfter time.Duration
145 maxResendAfter time.Duration145 maxResendAfter time.Duration
146 maxResendPerTick int146 maxResendPerTick int
147+ srtt time.Duration
148+ rttvar time.Duration
149+ rto time.Duration
147 writeTimeout time.Duration150 writeTimeout time.Duration
148 degradedAfter time.Duration151 degradedAfter time.Duration
149 probeAfter time.Duration152 probeAfter time.Duration
@@ -176,10 +179,11 @@ type Transport struct {
176 }179 }
177 180
178 type pending struct {181 type pending struct {
179- frame ports.Frame182+ frame ports.Frame
180- first time.Time183+ first time.Time
181- last time.Time184+ last time.Time
182- attempts int185+ attempts int
186+ retransmitted bool
183 }187 }
184 188
185 func NewTransport(pc net.PacketConn, peer net.Addr, key []byte, sendDir, recvDir uint32) (*Transport, error) {189 func NewTransport(pc net.PacketConn, peer net.Addr, key []byte, sendDir, recvDir uint32) (*Transport, error) {
@@ -209,6 +213,7 @@ func NewTransportWithOptions(pc net.PacketConn, peer net.Addr, key []byte, sendD
209 resendAfter: opts.ResendAfter,213 resendAfter: opts.ResendAfter,
210 maxResendAfter: opts.MaxResendAfter,214 maxResendAfter: opts.MaxResendAfter,
211 maxResendPerTick: opts.MaxResendPerTick,215 maxResendPerTick: opts.MaxResendPerTick,
216+ rto: opts.ResendAfter,
212 writeTimeout: opts.WriteTimeout,217 writeTimeout: opts.WriteTimeout,
213 degradedAfter: opts.DegradedAfter,218 degradedAfter: opts.DegradedAfter,
214 probeAfter: opts.ProbeAfter,219 probeAfter: opts.ProbeAfter,
@@ -499,8 +504,18 @@ func (t *Transport) handleRecord(p []byte) {
499 }504 }
500 seq := binary.BigEndian.Uint64(p[1:])505 seq := binary.BigEndian.Uint64(p[1:])
501 t.mu.Lock()506 t.mu.Lock()
502- if _, ok := t.pending[seq]; ok {507+ acked := false
503- delete(t.pending, seq)508+ for pendingSeq, p := range t.pending {
509+ if pendingSeq > seq {
510+ continue
511+ }
512+ if pendingSeq == seq && p != nil && !p.retransmitted {
513+ t.updateRTTLocked(t.clock.Now().Sub(p.first))
514+ }
515+ delete(t.pending, pendingSeq)
516+ acked = true
517+ }
518+ if acked {
504 t.notifySendWaitersLocked()519 t.notifySendWaitersLocked()
505 }520 }
506 t.mu.Unlock()521 t.mu.Unlock()
@@ -530,9 +545,9 @@ func (t *Transport) handleRecord(p []byte) {
530 return545 return
531 }546 }
532 if reliable {547 if reliable {
533- ack, queued := t.enqueueReliable(seq, f)548+ ackSeq, ack, queued := t.enqueueReliable(seq, f)
534 if ack {549 if ack {
535- t.sendAck(seq)550+ t.sendAck(ackSeq)
536 }551 }
537 if queued {552 if queued {
538 t.signalDelivery()553 t.signalDelivery()
@@ -543,20 +558,31 @@ func (t *Transport) handleRecord(p []byte) {
543 }558 }
544 }559 }
545 560
546-func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ack bool, queued bool) {561+func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ackSeq uint64, ack bool, queued bool) {
547 t.deliverMu.Lock()562 t.deliverMu.Lock()
548 defer t.deliverMu.Unlock()563 defer t.deliverMu.Unlock()
549 if seq < t.nextRecvSeq {564 if seq < t.nextRecvSeq {
550- return true, false565+ return t.highestContiguousRecvLocked(), true, false
551 }566 }
552 if _, exists := t.recvBuf[seq]; exists {567 if _, exists := t.recvBuf[seq]; exists {
553- return true, false568+ return t.highestContiguousRecvLocked(), true, false
554 }569 }
555 if len(t.recvBuf) >= t.maxRecvBuffer && seq != t.nextRecvSeq {570 if len(t.recvBuf) >= t.maxRecvBuffer && seq != t.nextRecvSeq {
556- return false, false571+ return 0, false, false
557 }572 }
558 t.recvBuf[seq] = f573 t.recvBuf[seq] = f
559- return true, true574+ return t.highestContiguousRecvLocked(), true, true
575+}
576+
577+func (t *Transport) highestContiguousRecvLocked() uint64 {
578+ seq := t.nextRecvSeq
579+ for {
580+ if _, ok := t.recvBuf[seq]; !ok {
581+ break
582+ }
583+ seq++
584+ }
585+ return seq - 1
560 }586 }
561 587
562 func (t *Transport) signalDelivery() {588 func (t *Transport) signalDelivery() {
@@ -662,6 +688,7 @@ func (t *Transport) resendPending() {
662 }688 }
663 p.last = now689 p.last = now
664 p.attempts++690 p.attempts++
691+ p.retransmitted = true
665 resend = append(resend, struct {692 resend = append(resend, struct {
666 seq uint64693 seq uint64
667 f ports.Frame694 f ports.Frame
@@ -675,7 +702,10 @@ func (t *Transport) resendPending() {
675 }702 }
676 703
677 func (t *Transport) resendDelayLocked(p *pending) time.Duration {704 func (t *Transport) resendDelayLocked(p *pending) time.Duration {
678- d := t.resendAfter705+ d := t.rto
706+ if d <= 0 {
707+ d = t.resendAfter
708+ }
679 for i := 0; i < p.attempts && d < t.maxResendAfter; i++ {709 for i := 0; i < p.attempts && d < t.maxResendAfter; i++ {
680 d *= 2710 d *= 2
681 if d > t.maxResendAfter {711 if d > t.maxResendAfter {
@@ -685,6 +715,35 @@ func (t *Transport) resendDelayLocked(p *pending) time.Duration {
685 return d715 return d
686 }716 }
687 717
718+func (t *Transport) updateRTTLocked(sample time.Duration) {
719+ if sample <= 0 {
720+ return
721+ }
722+ if t.srtt <= 0 {
723+ t.srtt = sample
724+ t.rttvar = sample / 2
725+ } else {
726+ delta := t.srtt - sample
727+ if delta < 0 {
728+ delta = -delta
729+ }
730+ t.rttvar = (3*t.rttvar + delta) / 4
731+ t.srtt = (7*t.srtt + sample) / 8
732+ }
733+ variation := 4 * t.rttvar
734+ if variation < time.Millisecond {
735+ variation = time.Millisecond
736+ }
737+ rto := t.srtt + variation
738+ if rto < t.resendAfter {
739+ rto = t.resendAfter
740+ }
741+ if rto > t.maxResendAfter {
742+ rto = t.maxResendAfter
743+ }
744+ t.rto = rto
745+}
746+
688 func (t *Transport) hopPacketConnOnce() {747 func (t *Transport) hopPacketConnOnce() {
689 t.mu.Lock()748 t.mu.Lock()
690 if t.rebind == nil || t.hoppedOffline || t.closed {749 if t.rebind == nil || t.hoppedOffline || t.closed {
M internal/adapters/dgram/transport_test.go +90 -2
@@ -511,11 +511,11 @@ func TestServerWithoutRebindDoesNotHopPorts(t *testing.T) {
511511
512512 func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) {
513513 tr := &Transport{maxRecvBuffer: 1, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}
514- ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")})
514+ _, ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")})
515515 if !ack || !queued {
516516 t.Fatalf("future enqueue ack=%v queued=%v, want true/true", ack, queued)
517517 }
518- ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")})
518+ _, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")})
519519 if !ack || !queued {
520520 t.Fatalf("next enqueue ack=%v queued=%v, want true/true", ack, queued)
521521 }
@@ -624,6 +624,7 @@ func newResendTestTransport(t *testing.T, maxResendPerTick int) (*Transport, *at
624624 resendAfter: 100 * time.Millisecond,
625625 maxResendAfter: 500 * time.Millisecond,
626626 maxResendPerTick: maxResendPerTick,
627+ rto: 100 * time.Millisecond,
627628 clock: realClock{},
628629 }, &writes
629630 }
@@ -701,6 +702,93 @@ func TestRecvUnblocksWhenPeerDeadAfter(t *testing.T) {
701702 }
702703 }
703704
705+func TestCumulativeAckReleasesAllEarlierPendingFrames(t *testing.T) {
706+ tr, _ := newResendTestTransport(t, 10)
707+ now := tr.clock.Now()
708+ for seq := uint64(1); seq <= 3; seq++ {
709+ tr.pending[seq] = &pending{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(seq)}}, first: now, last: now}
710+ }
711+
712+ tr.handleRecord(ackRecord(3))
713+
714+ if got := len(tr.pending); got != 0 {
715+ t.Fatalf("pending after cumulative ack=%d, want 0", got)
716+ }
717+}
718+
719+func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {
720+ tr := &Transport{maxRecvBuffer: maxRecvBuffer, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}
721+
722+ ackSeq, ack, queued := tr.enqueueReliable(3, ports.Frame{Type: ports.MsgOutput, Payload: []byte("third")})
723+ if !ack || !queued {
724+ t.Fatalf("out-of-order enqueue ack=%v queued=%v, want true/true", ack, queued)
725+ }
726+ if got, want := ackSeq, uint64(0); got != want {
727+ t.Fatalf("out-of-order ack=%d, want highest contiguous %d", got, want)
728+ }
729+
730+ ackSeq, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("first")})
731+ if !ack || !queued {
732+ t.Fatalf("first enqueue ack=%v queued=%v, want true/true", ack, queued)
733+ }
734+ if got, want := ackSeq, uint64(1); got != want {
735+ t.Fatalf("first contiguous ack=%d, want %d", got, want)
736+ }
737+
738+ ackSeq, ack, queued = tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("second")})
739+ if !ack || !queued {
740+ t.Fatalf("gap fill enqueue ack=%v queued=%v, want true/true", ack, queued)
741+ }
742+ if got, want := ackSeq, uint64(3); got != want {
743+ t.Fatalf("filled gap ack=%d, want %d", got, want)
744+ }
745+}
746+
747+func TestRTTEstimatorUpdatesRTOFromAckSample(t *testing.T) {
748+ clk := fixedClock{now: time.Date(2030, 1, 2, 3, 4, 5, 0, time.UTC)}
749+ tr, writes := newResendTestTransport(t, 10)
750+ tr.clock = clk
751+ tr.resendAfter = 100 * time.Millisecond
752+ tr.maxResendAfter = time.Second
753+ tr.rto = tr.resendAfter
754+ tr.pending[1] = &pending{frame: ports.Frame{Type: ports.MsgOutput}, first: clk.now.Add(-200 * time.Millisecond), last: clk.now.Add(-200 * time.Millisecond)}
755+
756+ tr.handleRecord(ackRecord(1))
757+
758+ if tr.rto <= tr.resendAfter {
759+ t.Fatalf("rto=%v, want above initial resend after RTT sample", tr.rto)
760+ }
761+ tr.pending[2] = &pending{frame: ports.Frame{Type: ports.MsgOutput}, first: clk.now, last: clk.now.Add(-(tr.rto - time.Millisecond))}
762+ tr.resendPending()
763+ if got := writes.Load(); got != 0 {
764+ t.Fatalf("writes before rto=%d, want 0", got)
765+ }
766+ tr.pending[2].last = clk.now.Add(-tr.rto)
767+ tr.resendPending()
768+ if got := writes.Load(); got != 1 {
769+ t.Fatalf("writes at rto=%d, want 1", got)
770+ }
771+}
772+
773+func TestRTTEstimatorIgnoresRetransmittedFramesAfterRecovery(t *testing.T) {
774+ clk := fixedClock{now: time.Date(2030, 1, 2, 3, 4, 5, 0, time.UTC)}
775+ tr, _ := newResendTestTransport(t, 10)
776+ tr.clock = clk
777+ tr.resendAfter = 100 * time.Millisecond
778+ tr.maxResendAfter = time.Second
779+ tr.rto = tr.resendAfter
780+ tr.pending[1] = &pending{frame: ports.Frame{Type: ports.MsgOutput}, first: clk.now.Add(-200 * time.Millisecond), last: clk.now.Add(-tr.resendAfter)}
781+ tr.resendPending()
782+ tr.linkState = ports.LinkStateOffline
783+
784+ tr.setLinkState(ports.LinkStateConnected, nil)
785+ tr.handleRecord(ackRecord(1))
786+
787+ if tr.rto != tr.resendAfter || tr.srtt != 0 || tr.rttvar != 0 {
788+ t.Fatalf("rtt sampled retransmitted frame after recovery: rto=%v srtt=%v rttvar=%v", tr.rto, tr.srtt, tr.rttvar)
789+ }
790+}
791+
704792 func TestPendingReliableQueueBackpressuresUntilAck(t *testing.T) {
705793 aPC, bPC := newPair()
706794 aPC.drop = func(_ []byte, addr net.Addr) bool { return addr.String() == "b" }
@@ -511,11 +511,11 @@ func TestServerWithoutRebindDoesNotHopPorts(t *testing.T) {
511 511
512 func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) {512 func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) {
513 tr := &Transport{maxRecvBuffer: 1, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}513 tr := &Transport{maxRecvBuffer: 1, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}
514- ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")})514+ _, ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")})
515 if !ack || !queued {515 if !ack || !queued {
516 t.Fatalf("future enqueue ack=%v queued=%v, want true/true", ack, queued)516 t.Fatalf("future enqueue ack=%v queued=%v, want true/true", ack, queued)
517 }517 }
518- ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")})518+ _, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")})
519 if !ack || !queued {519 if !ack || !queued {
520 t.Fatalf("next enqueue ack=%v queued=%v, want true/true", ack, queued)520 t.Fatalf("next enqueue ack=%v queued=%v, want true/true", ack, queued)
521 }521 }
@@ -624,6 +624,7 @@ func newResendTestTransport(t *testing.T, maxResendPerTick int) (*Transport, *at
624 resendAfter: 100 * time.Millisecond,624 resendAfter: 100 * time.Millisecond,
625 maxResendAfter: 500 * time.Millisecond,625 maxResendAfter: 500 * time.Millisecond,
626 maxResendPerTick: maxResendPerTick,626 maxResendPerTick: maxResendPerTick,
627+ rto: 100 * time.Millisecond,
627 clock: realClock{},628 clock: realClock{},
628 }, &writes629 }, &writes
629 }630 }
@@ -701,6 +702,93 @@ func TestRecvUnblocksWhenPeerDeadAfter(t *testing.T) {
701 }702 }
702 }703 }
703 704
705+func TestCumulativeAckReleasesAllEarlierPendingFrames(t *testing.T) {
706+ tr, _ := newResendTestTransport(t, 10)
707+ now := tr.clock.Now()
708+ for seq := uint64(1); seq <= 3; seq++ {
709+ tr.pending[seq] = &pending{frame: ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(seq)}}, first: now, last: now}
710+ }
711+
712+ tr.handleRecord(ackRecord(3))
713+
714+ if got := len(tr.pending); got != 0 {
715+ t.Fatalf("pending after cumulative ack=%d, want 0", got)
716+ }
717+}
718+
719+func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) {
720+ tr := &Transport{maxRecvBuffer: maxRecvBuffer, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)}
721+
722+ ackSeq, ack, queued := tr.enqueueReliable(3, ports.Frame{Type: ports.MsgOutput, Payload: []byte("third")})
723+ if !ack || !queued {
724+ t.Fatalf("out-of-order enqueue ack=%v queued=%v, want true/true", ack, queued)
725+ }
726+ if got, want := ackSeq, uint64(0); got != want {
727+ t.Fatalf("out-of-order ack=%d, want highest contiguous %d", got, want)
728+ }
729+
730+ ackSeq, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("first")})
731+ if !ack || !queued {
732+ t.Fatalf("first enqueue ack=%v queued=%v, want true/true", ack, queued)
733+ }
734+ if got, want := ackSeq, uint64(1); got != want {
735+ t.Fatalf("first contiguous ack=%d, want %d", got, want)
736+ }
737+
738+ ackSeq, ack, queued = tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("second")})
739+ if !ack || !queued {
740+ t.Fatalf("gap fill enqueue ack=%v queued=%v, want true/true", ack, queued)
741+ }
742+ if got, want := ackSeq, uint64(3); got != want {
743+ t.Fatalf("filled gap ack=%d, want %d", got, want)
744+ }
745+}
746+
747+func TestRTTEstimatorUpdatesRTOFromAckSample(t *testing.T) {
748+ clk := fixedClock{now: time.Date(2030, 1, 2, 3, 4, 5, 0, time.UTC)}
749+ tr, writes := newResendTestTransport(t, 10)
750+ tr.clock = clk
751+ tr.resendAfter = 100 * time.Millisecond
752+ tr.maxResendAfter = time.Second
753+ tr.rto = tr.resendAfter
754+ tr.pending[1] = &pending{frame: ports.Frame{Type: ports.MsgOutput}, first: clk.now.Add(-200 * time.Millisecond), last: clk.now.Add(-200 * time.Millisecond)}
755+
756+ tr.handleRecord(ackRecord(1))
757+
758+ if tr.rto <= tr.resendAfter {
759+ t.Fatalf("rto=%v, want above initial resend after RTT sample", tr.rto)
760+ }
761+ tr.pending[2] = &pending{frame: ports.Frame{Type: ports.MsgOutput}, first: clk.now, last: clk.now.Add(-(tr.rto - time.Millisecond))}
762+ tr.resendPending()
763+ if got := writes.Load(); got != 0 {
764+ t.Fatalf("writes before rto=%d, want 0", got)
765+ }
766+ tr.pending[2].last = clk.now.Add(-tr.rto)
767+ tr.resendPending()
768+ if got := writes.Load(); got != 1 {
769+ t.Fatalf("writes at rto=%d, want 1", got)
770+ }
771+}
772+
773+func TestRTTEstimatorIgnoresRetransmittedFramesAfterRecovery(t *testing.T) {
774+ clk := fixedClock{now: time.Date(2030, 1, 2, 3, 4, 5, 0, time.UTC)}
775+ tr, _ := newResendTestTransport(t, 10)
776+ tr.clock = clk
777+ tr.resendAfter = 100 * time.Millisecond
778+ tr.maxResendAfter = time.Second
779+ tr.rto = tr.resendAfter
780+ tr.pending[1] = &pending{frame: ports.Frame{Type: ports.MsgOutput}, first: clk.now.Add(-200 * time.Millisecond), last: clk.now.Add(-tr.resendAfter)}
781+ tr.resendPending()
782+ tr.linkState = ports.LinkStateOffline
783+
784+ tr.setLinkState(ports.LinkStateConnected, nil)
785+ tr.handleRecord(ackRecord(1))
786+
787+ if tr.rto != tr.resendAfter || tr.srtt != 0 || tr.rttvar != 0 {
788+ t.Fatalf("rtt sampled retransmitted frame after recovery: rto=%v srtt=%v rttvar=%v", tr.rto, tr.srtt, tr.rttvar)
789+ }
790+}
791+
704 func TestPendingReliableQueueBackpressuresUntilAck(t *testing.T) {792 func TestPendingReliableQueueBackpressuresUntilAck(t *testing.T) {
705 aPC, bPC := newPair()793 aPC, bPC := newPair()
706 aPC.drop = func(_ []byte, addr net.Addr) bool { return addr.String() == "b" }794 aPC.drop = func(_ []byte, addr net.Addr) bool { return addr.String() == "b" }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close