Skip to content

Commit edfbbab

Browse files
committed
gbn: add timeout manager
The timeout manager is a manager for all timeout values used by the different components of the gbn package. The timeout manager enables the functionality of dynamically changing the resend timeout value based on how long it takes to receive an response from the counterparty. To make the gbn package more modular, we'll move the responsibility of managing the timeout values of the gbn package to the timeout manager in the next commit.
1 parent cc8194f commit edfbbab

File tree

2 files changed

+350
-0
lines changed

2 files changed

+350
-0
lines changed

gbn/timeout_manager.go

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package gbn
2+
3+
import (
4+
"math"
5+
"sync"
6+
"time"
7+
)
8+
9+
const (
10+
defaultTMHandshakeTimeout = 100 * time.Millisecond
11+
defaultTMResendTimeout = 100 * time.Millisecond
12+
minimumResendTimeout = 100 * time.Millisecond
13+
defaultTMFinSendTimeout = 1000 * time.Millisecond
14+
defaultResendMultiplier = 5
15+
DefaultTMSendTimeout = math.MaxInt64
16+
DefaultTMRecvTimeout = math.MaxInt64
17+
)
18+
19+
// TimeoutManager manages the different timeouts used by the gbn package.
20+
type TimeoutManager struct {
21+
// isServer is used to indicate whether the timeout manager is used
22+
// by the server or the client.
23+
isServer bool
24+
25+
// useStaticTimeout is used to indicate whether the resendTimeout
26+
// has been manually set, and if so, should not be updated dynamically.
27+
useStaticTimeout bool
28+
29+
// resendTimeout defines the current resend timeout used by the
30+
// timeout manager.
31+
// The resend timeout is the duration that will be waited before
32+
// resending the packets in the current queue. The timeout is
33+
// dynamically set during the handshake process, and is set to the time
34+
// it took for the other party to respond, multiplied by the
35+
// resendMultiplier.
36+
resendTimeout time.Duration
37+
38+
// resendMultiplier defines the multiplier used when multiplying the
39+
// duration it took for the other party to respond when setting the
40+
// resendTimeout dynamically during the handshake.
41+
resendMultiplier int
42+
43+
// latestSentSYNTime is used to keep track of the time when the latest
44+
// SYN message was sent. This is used to dynamically set the resend
45+
// timeout, based on how long it took for the other party to respond to
46+
// the SYN message.
47+
latestSentSYNTime time.Time
48+
49+
// handshakeTimeout is the time after which the server or client
50+
// will abort and restart the handshake if the expected response is
51+
// not received from the peer.
52+
handshakeTimeout time.Duration
53+
54+
// finSendTimeout is the timeout after which the created context for
55+
// sending a FIN message will be time out.
56+
finSendTimeout time.Duration
57+
58+
// sendTimeout defines the max time we will wait to send a msg before
59+
// timing out.
60+
sendTimeout time.Duration
61+
62+
// recvTimeout defines the max time we will wait to receive a msg before
63+
// timing out.
64+
recvTimeout time.Duration
65+
66+
// timeoutManagerMu should be locked when updating or accessing any of
67+
// timeout manager's timeout fields.
68+
timeoutManagerMu sync.RWMutex
69+
}
70+
71+
// NewTimeOutManager creates a new timeout manager.
72+
func NewTimeOutManager(isServer bool) *TimeoutManager {
73+
return &TimeoutManager{
74+
isServer: isServer,
75+
resendTimeout: defaultTMResendTimeout,
76+
handshakeTimeout: defaultTMHandshakeTimeout,
77+
useStaticTimeout: false,
78+
resendMultiplier: defaultResendMultiplier,
79+
finSendTimeout: defaultTMFinSendTimeout,
80+
recvTimeout: DefaultTMRecvTimeout,
81+
sendTimeout: DefaultTMSendTimeout,
82+
}
83+
}
84+
85+
// Sent is should be called when a message is sent by the connection.
86+
func (m *TimeoutManager) Sent(msg Message) {
87+
// TODO(viktor): In the future, we may want to use this to keep track of
88+
// the time it took for the other party to respond to other types of
89+
// messages than the handshake, and dynamically keep updating the resend
90+
// timeout to ensure that it reflects the current response time.
91+
switch msg.(type) { //nolint:gocritic
92+
case *PacketSYN:
93+
// Note that we may send multiple SYN messages before receiving
94+
// a response. Therefore, this field might be updated multiple
95+
// times.
96+
m.latestSentSYNTime = time.Now()
97+
}
98+
}
99+
100+
// Received is should be called when a message is received by the connection.
101+
func (m *TimeoutManager) Received(msg Message) {
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 > minimumResendTimeout {
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+
146+
// GetResendTimeout returns the current resend timeout.
147+
func (m *TimeoutManager) GetResendTimeout() time.Duration {
148+
m.timeoutManagerMu.RLock()
149+
defer m.timeoutManagerMu.RUnlock()
150+
151+
return m.resendTimeout
152+
}
153+
154+
// GetHandshakeTimeout returns the handshake timeout.
155+
func (m *TimeoutManager) GetHandshakeTimeout() time.Duration {
156+
m.timeoutManagerMu.RLock()
157+
defer m.timeoutManagerMu.RUnlock()
158+
159+
return m.handshakeTimeout
160+
}
161+
162+
// GetFinSendTimeout returns the fin send timeout.
163+
func (m *TimeoutManager) GetFinSendTimeout() time.Duration {
164+
m.timeoutManagerMu.RLock()
165+
defer m.timeoutManagerMu.RUnlock()
166+
167+
return m.finSendTimeout
168+
}
169+
170+
// GetSendTimeout returns the send timeout.
171+
func (m *TimeoutManager) GetSendTimeout() time.Duration {
172+
m.timeoutManagerMu.RLock()
173+
defer m.timeoutManagerMu.RUnlock()
174+
175+
return m.sendTimeout
176+
}
177+
178+
// GetRecvTimeout returns the recv timeout.
179+
func (m *TimeoutManager) GetRecvTimeout() time.Duration {
180+
m.timeoutManagerMu.RLock()
181+
defer m.timeoutManagerMu.RUnlock()
182+
183+
return m.recvTimeout
184+
}
185+
186+
// SetStaticResendTimeout sets the resend timeout to the given value, and also
187+
// marks the timeout manager as using a static resend timeout, which will mean
188+
// that the resend timeout will not be updated dynamically.
189+
func (m *TimeoutManager) SetStaticResendTimeout(resendTimeout time.Duration) {
190+
m.timeoutManagerMu.Lock()
191+
defer m.timeoutManagerMu.Unlock()
192+
193+
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.timeoutManagerMu.Lock()
201+
defer m.timeoutManagerMu.Unlock()
202+
203+
m.resendMultiplier = resendMultiplier
204+
}
205+
206+
// SetHandshakeTimeout sets the handshake timeout.
207+
func (m *TimeoutManager) SetHandshakeTimeout(handshakeTimeout time.Duration) {
208+
m.timeoutManagerMu.Lock()
209+
defer m.timeoutManagerMu.Unlock()
210+
211+
m.handshakeTimeout = handshakeTimeout
212+
}
213+
214+
// SetSendTimeout sets the send timeout.
215+
func (m *TimeoutManager) SetSendTimeout(timeout time.Duration) {
216+
m.timeoutManagerMu.Lock()
217+
defer m.timeoutManagerMu.Unlock()
218+
219+
m.sendTimeout = timeout
220+
}
221+
222+
// SetRecvTimeout sets the receive timeout.
223+
func (m *TimeoutManager) SetRecvTimeout(timeout time.Duration) {
224+
m.timeoutManagerMu.Lock()
225+
defer m.timeoutManagerMu.Unlock()
226+
227+
m.recvTimeout = timeout
228+
}

gbn/timeout_manager_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package gbn
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/lightningnetwork/lnd/lntest/wait"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
// TestDynamicTimeout ensures that the resend timeout is dynamically set as
12+
// expected during for the timeout manager.
13+
func TestDynamicTimeout(t *testing.T) {
14+
t.Parallel()
15+
16+
// Create a new timeout manager to use for the test.
17+
tm := NewTimeOutManager(false)
18+
19+
// First, we'll ensure that the resend timeout doesn't change if we
20+
// don't send and receive messages.
21+
noResendTimeoutChange(t, tm, time.Second)
22+
23+
// Next, we'll simulate that a SYN message has been sent and received.
24+
// This should change the resend timeout given that the new timeout is
25+
// greater than the minimum allowed timeout.
26+
initialResendTimeout := tm.GetResendTimeout()
27+
28+
sendAndReceive(t, tm)
29+
30+
// The resend timeout should now have dynamically changed.
31+
resendTimeout := tm.GetResendTimeout()
32+
require.NotEqual(t, initialResendTimeout, resendTimeout)
33+
34+
// Let's also test that the resend timeout is dynamically set to the
35+
// expected value, and that the resend multiplier works as expected. If
36+
// we set resend multiplier to 10, then send and receive a response
37+
// after 1 second, then the resend timeout should be around 10 seconds.
38+
tm.SetResendMultiplier(10)
39+
40+
sendAndReceive(t, tm)
41+
42+
// As it takes a short amount of time to simulate the send and receive
43+
// of the message, we'll accept a set resend timeout within a range of
44+
// 10-11 seconds as correct.
45+
resendTimeout = tm.GetResendTimeout()
46+
require.GreaterOrEqual(t, resendTimeout, time.Second*10)
47+
require.Less(t, resendTimeout, time.Second*11)
48+
49+
// Finally, we'll test that the resend timeout isn't dynamically set if
50+
// the new timeout is less than the minimum allowed resend timeout.
51+
tm.SetResendMultiplier(1)
52+
53+
sendAndReceiveWithDuration(t, tm, time.Millisecond*50)
54+
55+
unchangedResendTimeout := tm.GetResendTimeout()
56+
require.Equal(t, resendTimeout, unchangedResendTimeout)
57+
}
58+
59+
// TestStaticTimeout ensures that the resend timeout isn't dynamically set if a
60+
// static timeout has been set.
61+
func TestStaticTimeout(t *testing.T) {
62+
t.Parallel()
63+
64+
// Create a new timeout manager to use for the test.
65+
tm := NewTimeOutManager(false)
66+
staticTimeout := time.Second * 2
67+
68+
// Set a static timeout for the timeout manager.
69+
tm.SetStaticResendTimeout(staticTimeout)
70+
71+
// Then ensure that the resend timeout isn't dynamically set if we send
72+
// and receive messages after setting a static timeout.
73+
sendAndReceive(t, tm)
74+
75+
resendTimeout := tm.GetResendTimeout()
76+
require.Equal(t, staticTimeout, resendTimeout)
77+
}
78+
79+
// sendAndReceive simulates that a SYN message has been sent for the passed the
80+
// timeout manager, and then waits for one second before a simulating the SYN
81+
// response. While waiting, the function asserts that the resend timeout hasn't
82+
// changed.
83+
func sendAndReceive(t *testing.T, tm *TimeoutManager) {
84+
t.Helper()
85+
86+
sendAndReceiveWithDuration(t, tm, time.Second)
87+
}
88+
89+
// sendAndReceive simulates that a SYN message has been sent for the passed the
90+
// timeout manager, and then waits for specified delay before a simulating the
91+
// SYN response. While waiting, the function asserts that the resend timeout
92+
// hasn't changed.
93+
func sendAndReceiveWithDuration(t *testing.T, tm *TimeoutManager,
94+
responseDelay time.Duration) {
95+
96+
t.Helper()
97+
98+
synMsg := &PacketSYN{N: 20}
99+
100+
tm.Sent(synMsg)
101+
102+
noResendTimeoutChange(t, tm, responseDelay)
103+
104+
tm.Received(synMsg)
105+
}
106+
107+
// noResendTimeoutChange asserts that the resend timeout hasn't changed for the
108+
// passed timeout manager for the specified duration.
109+
func noResendTimeoutChange(t *testing.T, tm *TimeoutManager,
110+
duration time.Duration) {
111+
112+
t.Helper()
113+
114+
resendTimeout := tm.GetResendTimeout()
115+
116+
err := wait.Invariant(func() bool {
117+
currentResendTimeout := tm.GetResendTimeout()
118+
119+
return resendTimeout == currentResendTimeout
120+
}, duration)
121+
require.NoError(t, err)
122+
}

0 commit comments

Comments
 (0)