Skip to content

Commit 6204394

Browse files
committed
gbn: make resend timeout dynamic
Prior to this commit, the timeout before a client resends the queue of packets was always a fixed value. This fixed timeout isn't suitable for all clients as the latency for different clients varies. With this commit, we instead set the resend timeout based on how long it took for the other party to respond during the handshake process. The timeout is set to the time it took for the server to respond multiplied by the resendMultiplier, unless the duration is shorter than the default resend timeout. If the the resend timeout has been manually set, the resend timeout will always be set to that value, and won't be dynamically set.
1 parent c21b195 commit 6204394

File tree

5 files changed

+115
-3
lines changed

5 files changed

+115
-3
lines changed

gbn/gbn_client.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ handshake:
110110
return err
111111
}
112112

113+
// Notify the timeout manager that we sent a SYN.
114+
g.timeoutManager.Sent(msg)
115+
113116
for {
114117
// Wait for SYN
115118
log.Debugf("Client waiting for SYN")
@@ -161,6 +164,10 @@ handshake:
161164
return io.EOF
162165
}
163166

167+
// Notify the timeout manager we've received the SYN response from the
168+
// counterparty.
169+
g.timeoutManager.Received(resp)
170+
164171
// Send SYNACK
165172
log.Debugf("Client sending SYNACK")
166173
synack, err := new(PacketSYNACK).Serialize()

gbn/gbn_conn.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,9 @@ func (g *GoBackNConn) sendPacket(ctx context.Context, msg Message) error {
351351
return fmt.Errorf("error calling sendToStream: %s", err)
352352
}
353353

354+
// Notify the timeout manager that a message has been sent.
355+
g.timeoutManager.Sent(msg)
356+
354357
return nil
355358
}
356359

gbn/gbn_server.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ func (g *GoBackNConn) serverHandshake() error { // nolint:gocyclo
128128
return err
129129
}
130130

131+
// Notify the timeout manager that we sent a SYN.
132+
g.timeoutManager.Sent(msg)
133+
131134
// Wait for SYNACK
132135
log.Debugf("Waiting for client SYNACK")
133136
select {
@@ -160,6 +163,10 @@ func (g *GoBackNConn) serverHandshake() error { // nolint:gocyclo
160163

161164
switch msg.(type) {
162165
case *PacketSYNACK:
166+
// Notify the timeout manager we've received the SYNACK
167+
// response from the counterparty.
168+
g.timeoutManager.Received(msg)
169+
163170
break
164171
case *PacketSYN:
165172
log.Debugf("Received SYN. Resend SYN.")

gbn/options.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,18 @@ func WithMaxSendSize(size int) Option {
1818
// for ACKs before resending the queue.
1919
func WithTimeout(timeout time.Duration) Option {
2020
return func(conn *GoBackNConn) {
21-
conn.timeoutManager.SetResendTimeout(timeout)
21+
conn.timeoutManager.SetStaticResendTimeout(timeout)
22+
}
23+
}
24+
25+
// WithResendMultiplier is used to set the resend multiplier. This is the
26+
// multiplier we use when dynamically setting the resend timeout, based on how
27+
// long it took for other party to respond during the handshake.
28+
// Note that when setting the resend timeout manually with the WithTimeout
29+
// option, this option will have no effect.
30+
func WithResendMultiplier(multiplier int) Option {
31+
return func(conn *GoBackNConn) {
32+
conn.timeoutManager.SetResendMultiplier(multiplier)
2233
}
2334
}
2435

gbn/timeout_manager.go

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const (
1010
defaultHandshakeTimeout = 100 * time.Millisecond
1111
defaultResendTimeout = 100 * time.Millisecond
1212
finSendTimeout = 1000 * time.Millisecond
13+
defaultResendMultiplier = 5
1314
DefaultSendTimeout = math.MaxInt64
1415
DefaultRecvTimeout = math.MaxInt64
1516
)
@@ -19,6 +20,10 @@ type TimeoutManager struct {
1920
// by the server or the client.
2021
isServer bool
2122

23+
// useStaticTimeout is used to indicate whether the resendTimeout
24+
// has been manually set, and if so, should not be updated dynamically.
25+
useStaticTimeout bool
26+
2227
// resendTimeout defines the current resend timeout used by the
2328
// timeout manager.
2429
// The resend timeout is the duration that will be waited before
@@ -29,6 +34,12 @@ type TimeoutManager struct {
2934
resendTimeout time.Duration
3035
resendTimeoutMu sync.RWMutex
3136

37+
// resendMultiplier defines the multiplier used when multiplying the
38+
// duration it took for the other party to respond when setting the
39+
// resendTimeout dynamically during the handshake.
40+
resendMultiplier int
41+
resendMultiplierMu sync.RWMutex
42+
3243
// latestSentSYNTime is used to keep track of the time when the latest
3344
// SYN message was sent. This is used to dynamically set the resend
3445
// timeout, based on how long it took for the other party to respond to
@@ -62,11 +73,74 @@ func NewTimeOutManager(isServer bool) *TimeoutManager {
6273
isServer: isServer,
6374
resendTimeout: defaultResendTimeout,
6475
handshakeTimeout: defaultHandshakeTimeout,
76+
useStaticTimeout: false,
77+
resendMultiplier: defaultResendMultiplier,
6578
recvTimeout: DefaultRecvTimeout,
6679
sendTimeout: DefaultSendTimeout,
6780
}
6881
}
6982

83+
// Sent is should be called when a message is sent by the connection.
84+
func (m *TimeoutManager) Sent(msg Message) {
85+
// TODO(viktor): In the future, we may want to use this to keep track of
86+
// the time it took for the other party to respond to other types of
87+
// messages than the handshake, and dynamically keep updating the resend
88+
// timeout to ensure that it reflects the current response time.
89+
switch msg.(type) { //nolint:gocritic
90+
case *PacketSYN:
91+
// Note that we may send multiple SYN messages before receiving
92+
// a response. Therefore, this field might be updated multiple
93+
// times.
94+
m.latestSentSYNTime = time.Now()
95+
}
96+
}
97+
98+
// Received is should be called when a message is received by the connection.
99+
func (m *TimeoutManager) Received(msg Message) {
100+
// TODO(viktor): In the future, we may want to use this to keep track of
101+
// the time it took for the other party to respond to other types of
102+
// messages than the handshake, and dynamically keep updating the resend
103+
// timeout to ensure that it reflects the current response time.
104+
switch msg.(type) {
105+
case *PacketSYN:
106+
if !m.isServer {
107+
m.updateResendTimeout(time.Since(m.latestSentSYNTime))
108+
}
109+
110+
case *PacketSYNACK:
111+
if m.isServer {
112+
m.updateResendTimeout(time.Since(m.latestSentSYNTime))
113+
}
114+
}
115+
}
116+
117+
// updateResendTimeout updates the resend timeout based on the given response
118+
// time. The resend timeout will be only be updated if the given response time
119+
// is greater than the default resend timeout, after being multiplied by the
120+
// resendMultiplier.
121+
// If a static timeout has been manually set, then this function will do be a
122+
// no-op.
123+
func (m *TimeoutManager) updateResendTimeout(responseTime time.Duration) {
124+
if m.useStaticTimeout {
125+
log.Tracef("Not increasing resendTimeout as it has been set " +
126+
"manually")
127+
128+
return
129+
}
130+
131+
multipliedTimeout := time.Duration(m.resendMultiplier) * responseTime
132+
133+
if multipliedTimeout > defaultResendTimeout {
134+
log.Tracef("Updating resendTimeout to %v", multipliedTimeout)
135+
136+
m.resendTimeout = multipliedTimeout
137+
} else {
138+
log.Tracef("Not updating resendTimeout to %v as it is not "+
139+
"greater than the minimum resendTimeout which is %v",
140+
multipliedTimeout, m.resendTimeout)
141+
}
142+
}
143+
70144
// GetResendTimeout returns the current resend timeout.
71145
func (m *TimeoutManager) GetResendTimeout() time.Duration {
72146
m.resendTimeoutMu.RLock()
@@ -107,14 +181,24 @@ func (m *TimeoutManager) GetRecvTimeout() time.Duration {
107181
return m.recvTimeout
108182
}
109183

110-
// SetResendTimeout sets the resend timeout to the given value, and also
184+
// SetStaticResendTimeout sets the resend timeout to the given value, and also
111185
// marks the timeout manager as using a static resend timeout, which will mean
112186
// that the resend timeout will not be updated dynamically.
113-
func (m *TimeoutManager) SetResendTimeout(resendTimeout time.Duration) {
187+
func (m *TimeoutManager) SetStaticResendTimeout(resendTimeout time.Duration) {
114188
m.resendTimeoutMu.Lock()
115189
defer m.resendTimeoutMu.Unlock()
116190

117191
m.resendTimeout = resendTimeout
192+
m.useStaticTimeout = true
193+
}
194+
195+
// SetResendMultiplier sets the resend multiplier used when dynamically
196+
// setting the resend timeout.
197+
func (m *TimeoutManager) SetResendMultiplier(resendMultiplier int) {
198+
m.resendMultiplierMu.Lock()
199+
defer m.resendMultiplierMu.Unlock()
200+
201+
m.resendMultiplier = resendMultiplier
118202
}
119203

120204
// SetHandshakeTimeout sets the handshake timeout.

0 commit comments

Comments
 (0)