Skip to content

Commit 438fcc5

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 a6da2fe commit 438fcc5

File tree

7 files changed

+123
-5
lines changed

7 files changed

+123
-5
lines changed

gbn/gbn_client.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ handshake:
105105
return err
106106
}
107107

108+
// Notify the timeout manager that we sent a SYN.
109+
g.timeoutManager.Sent(msg)
110+
108111
for {
109112
// Wait for SYN
110113
log.Debugf("Client waiting for SYN")
@@ -156,6 +159,10 @@ handshake:
156159
return io.EOF
157160
}
158161

162+
// Notify the timeout manager we've received the SYN response from the
163+
// counterparty.
164+
g.timeoutManager.Received(resp)
165+
159166
// Send SYNACK
160167
log.Debugf("Client sending SYNACK")
161168
synack, err := new(PacketSYNACK).Serialize()

gbn/gbn_conn.go

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

369+
// Notify the timeout manager that a message has been sent.
370+
g.timeoutManager.Sent(msg)
371+
369372
return nil
370373
}
371374

gbn/gbn_server.go

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

126+
// Notify the timeout manager that we sent a SYN.
127+
g.timeoutManager.Sent(msg)
128+
126129
// Wait for SYNACK
127130
log.Debugf("Waiting for client SYNACK")
128131
select {
@@ -155,6 +158,10 @@ func (g *GoBackNConn) serverHandshake() error { // nolint:gocyclo
155158

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

110-
// SetResendTimeout sets the resend timeout to the given value, and also
186+
// SetStaticResendTimeout sets the resend timeout to the given value, and also
111187
// marks the timeout manager as using a static resend timeout, which will mean
112188
// that the resend timeout will not be updated dynamically.
113-
func (m *TimeoutManager) SetResendTimeout(resendTimeout time.Duration) {
189+
func (m *TimeoutManager) SetStaticResendTimeout(resendTimeout time.Duration) {
114190
m.resendTimeoutMu.Lock()
115191
defer m.resendTimeoutMu.Unlock()
116192

117193
m.resendTimeout = resendTimeout
194+
m.useStaticTimeout = true
195+
}
196+
197+
// SetResendMultiplier sets the resend multiplier used when dynamically
198+
// setting the resend timeout.
199+
func (m *TimeoutManager) SetResendMultiplier(resendMultiplier int) {
200+
m.resendMultiplierMu.Lock()
201+
defer m.resendMultiplierMu.Unlock()
202+
203+
m.resendMultiplier = resendMultiplier
118204
}
119205

120206
// SetHandshakeTimeout sets the handshake timeout.

mailbox/client_conn.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ const (
4545
// to receive ACKS from the peer before resending the queue.
4646
gbnTimeout = 1000 * time.Millisecond
4747

48+
// gbnResendMultiplier is the multiplier that we want the gbn
49+
// connection to use when dynamically setting the resend timeout.
50+
gbnResendMultiplier = 5
51+
4852
// gbnN is the queue size, N, that the gbn server will use. The gbn
4953
// server will send up to N packets before requiring an ACK for the
5054
// first packet in the queue.
@@ -156,7 +160,7 @@ func NewClientConn(ctx context.Context, sid [64]byte, serverHost string,
156160
c := &ClientConn{
157161
transport: transport,
158162
gbnOptions: []gbn.Option{
159-
gbn.WithTimeout(gbnTimeout),
163+
gbn.WithResendMultiplier(gbnResendMultiplier),
160164
gbn.WithHandshakeTimeout(gbnHandshakeTimeout),
161165
gbn.WithKeepalivePing(
162166
gbnClientPingTimeout, gbnPongTimeout,

mailbox/server_conn.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func NewServerConn(ctx context.Context, serverHost string,
7777
cancel: cancel,
7878
quit: make(chan struct{}),
7979
gbnOptions: []gbn.Option{
80-
gbn.WithTimeout(gbnTimeout),
80+
gbn.WithResendMultiplier(gbnResendMultiplier),
8181
gbn.WithHandshakeTimeout(gbnHandshakeTimeout),
8282
gbn.WithKeepalivePing(
8383
gbnServerPingTimeout, gbnPongTimeout,

0 commit comments

Comments
 (0)