Skip to content

Commit 445d935

Browse files
authored
Merge pull request #87 from ViktorTigerstrom/2023-10-client-side-packeting-bug-fix
gbn: Sender side only packeting resend bug fix
2 parents fa60171 + 5e16121 commit 445d935

File tree

9 files changed

+742
-77
lines changed

9 files changed

+742
-77
lines changed

.golangci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ linters:
7070
# guidelines. See https://github.com/mvdan/gofumpt/issues/235.
7171
- gofumpt
7272

73+
# Disable gomnd even though we generally don't use magic numbers, but there
74+
# are exceptions where this improves readability.
75+
- gomnd
76+
7377
# Disable whitespace linter as it has conflict rules against our
7478
# contribution guidelines. See https://github.com/bombsimon/wsl/issues/109.
7579
#

gbn/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ type config struct {
3838
// packet.
3939
sendToStream sendBytesFunc
4040

41+
// onFIN is a callback that if set, will be called once a FIN packet has
42+
// been received and processed.
43+
onFIN func()
44+
4145
// handshakeTimeout is the time after which the server or client
4246
// will abort and restart the handshake if the expected response is
4347
// not received from the peer.

gbn/gbn_conn.go

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,10 @@ func newGoBackNConn(ctx context.Context, cfg *config,
8989
prefix := fmt.Sprintf("(%s)", loggerPrefix)
9090
plog := build.NewPrefixLog(prefix, log)
9191

92-
return &GoBackNConn{
93-
cfg: cfg,
94-
recvDataChan: make(chan *PacketData, cfg.n),
95-
sendDataChan: make(chan *PacketData),
96-
sendQueue: newQueue(
97-
cfg.n+1, defaultHandshakeTimeout, plog,
98-
),
92+
g := &GoBackNConn{
93+
cfg: cfg,
94+
recvDataChan: make(chan *PacketData, cfg.n),
95+
sendDataChan: make(chan *PacketData),
9996
recvTimeout: DefaultRecvTimeout,
10097
sendTimeout: DefaultSendTimeout,
10198
receivedACKSignal: make(chan struct{}),
@@ -106,6 +103,17 @@ func newGoBackNConn(ctx context.Context, cfg *config,
106103
log: plog,
107104
quit: make(chan struct{}),
108105
}
106+
107+
g.sendQueue = newQueue(&queueCfg{
108+
s: cfg.n + 1,
109+
timeout: cfg.resendTimeout,
110+
log: plog,
111+
sendPkt: func(packet *PacketData) error {
112+
return g.sendPacket(g.ctx, packet)
113+
},
114+
})
115+
116+
return g
109117
}
110118

111119
// setN sets the current N to use. This _must_ be set before the handshake is
@@ -114,7 +122,14 @@ func (g *GoBackNConn) setN(n uint8) {
114122
g.cfg.n = n
115123
g.cfg.s = n + 1
116124
g.recvDataChan = make(chan *PacketData, n)
117-
g.sendQueue = newQueue(n+1, defaultHandshakeTimeout, g.log)
125+
g.sendQueue = newQueue(&queueCfg{
126+
s: n + 1,
127+
timeout: g.cfg.resendTimeout,
128+
log: g.log,
129+
sendPkt: func(packet *PacketData) error {
130+
return g.sendPacket(g.ctx, packet)
131+
},
132+
})
118133
}
119134

120135
// SetSendTimeout sets the timeout used in the Send function.
@@ -320,6 +335,8 @@ func (g *GoBackNConn) Close() error {
320335
// initialisation.
321336
g.cancel()
322337

338+
g.sendQueue.stop()
339+
323340
g.wg.Wait()
324341

325342
if g.pingTicker != nil {
@@ -359,9 +376,28 @@ func (g *GoBackNConn) sendPacket(ctx context.Context, msg Message) error {
359376
func (g *GoBackNConn) sendPacketsForever() error {
360377
// resendQueue re-sends the current contents of the queue.
361378
resendQueue := func() error {
362-
return g.sendQueue.resend(func(packet *PacketData) error {
363-
return g.sendPacket(g.ctx, packet)
364-
})
379+
err := g.sendQueue.resend()
380+
if err != nil {
381+
return err
382+
}
383+
384+
// After resending the queue, we reset the resend ticker.
385+
// This is so that we don't immediately resend the queue again,
386+
// if the sendQueue.resend call above took a long time to
387+
// execute. That can happen if the function was awaiting the
388+
// expected ACK for a long time, or times out while awaiting the
389+
// catch up.
390+
g.resendTicker.Reset(g.cfg.resendTimeout)
391+
392+
// Also drain the resend signal channel, as resendTicker.Reset
393+
// doesn't drain the channel if the ticker ticked during the
394+
// sendQueue.resend() call above.
395+
select {
396+
case <-g.resendTicker.C:
397+
default:
398+
}
399+
400+
return nil
365401
}
366402

367403
for {
@@ -478,6 +514,8 @@ func (g *GoBackNConn) receivePacketsForever() error { // nolint:gocyclo
478514
g.pongTicker.Pause()
479515
}
480516

517+
g.resendTicker.Reset(g.cfg.resendTimeout)
518+
481519
switch m := msg.(type) {
482520
case *PacketData:
483521
switch m.Seq == g.recvSeq {
@@ -526,9 +564,19 @@ func (g *GoBackNConn) receivePacketsForever() error { // nolint:gocyclo
526564

527565
// If we recently sent a NACK for the same
528566
// sequence number then back off.
529-
if lastNackSeq == g.recvSeq &&
530-
time.Since(lastNackTime) <
531-
g.cfg.resendTimeout {
567+
// We wait 2 times the resendTimeout before
568+
// sending a new nack, as this case is likely
569+
// hit if the sender is currently resending
570+
// the queue, and therefore the threads that
571+
// are resending the queue is likely busy with
572+
// the resend, and therefore won't react to the
573+
// NACK we send here in time.
574+
sinceSent := time.Since(lastNackTime)
575+
recentlySent := sinceSent <
576+
g.cfg.resendTimeout*2
577+
578+
if lastNackSeq == g.recvSeq && recentlySent {
579+
g.log.Tracef("Recently sent NACK")
532580

533581
continue
534582
}
@@ -552,8 +600,6 @@ func (g *GoBackNConn) receivePacketsForever() error { // nolint:gocyclo
552600
case *PacketACK:
553601
gotValidACK := g.sendQueue.processACK(m.Seq)
554602
if gotValidACK {
555-
g.resendTicker.Reset(g.cfg.resendTimeout)
556-
557603
// Send a signal to indicate that new
558604
// ACKs have been received.
559605
select {
@@ -569,15 +615,12 @@ func (g *GoBackNConn) receivePacketsForever() error { // nolint:gocyclo
569615
// sent was dropped, or maybe we sent a duplicate
570616
// message. The NACK message contains the sequence
571617
// number that the receiver was expecting.
572-
inQueue, bumped := g.sendQueue.processNACK(m.Seq)
573-
574-
// If the NACK sequence number is not in our queue
575-
// then we ignore it. We must have received the ACK
576-
// for the sequence number in the meantime.
577-
if !inQueue {
578-
g.log.Tracef("NACK seq %d is not in the "+
579-
"queue. Ignoring", m.Seq)
618+
shouldResend, bumped := g.sendQueue.processNACK(m.Seq)
580619

620+
// If we don't need to resend the queue after processing
621+
// the NACK, we can continue without sending the resend
622+
// signal.
623+
if !shouldResend {
581624
continue
582625
}
583626

@@ -606,6 +649,10 @@ func (g *GoBackNConn) receivePacketsForever() error { // nolint:gocyclo
606649

607650
close(g.remoteClosed)
608651

652+
if g.cfg.onFIN != nil {
653+
g.cfg.onFIN()
654+
}
655+
609656
return errTransportClosing
610657

611658
default:

gbn/options.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,11 @@ func WithKeepalivePing(ping, pong time.Duration) Option {
4343
conn.pongTime = pong
4444
}
4545
}
46+
47+
// WithOnFIN is used to set the onFIN callback that will be called once a FIN
48+
// packet has been received and processed.
49+
func WithOnFIN(fn func()) Option {
50+
return func(conn *config) {
51+
conn.onFIN = fn
52+
}
53+
}

0 commit comments

Comments
 (0)