Skip to content

Commit 9429683

Browse files
Remove cap for number of reconnections per hour (#120)
* Remove cap for number of reconnections per hour * Change max backoff interval * Add tests * Re-enable tests
1 parent 5d207d8 commit 9429683

File tree

3 files changed

+29
-136
lines changed

3 files changed

+29
-136
lines changed

connection/connection.go

Lines changed: 17 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ import (
1717
)
1818

1919
var (
20-
// ErrTooManyReconnects is returned when the number of reconnections
21-
// has reached MaxReconnectionsTotal within MaxElapsedTime.
22-
ErrTooManyReconnects = errors.New("websocket cannot reconnect right now (too many attemps)")
2320
// ErrNotDialed is returned when WriteMessage is called, but
2421
// the websocket has not been created yet (call Dial).
2522
ErrNotDailed = errors.New("websocket not created yet, please call Dial()")
@@ -48,36 +45,25 @@ type Conn struct {
4845
// MaxElapsedTime is the amount of time after which the ExponentialBackOff
4946
// returns Stop. It never stops if MaxElapsedTime == 0.
5047
MaxElapsedTime time.Duration
51-
// MaxReconnectionsTotal is the maximum number of reconnections that can
52-
// happen within MaxReconnectionsTime.
53-
MaxReconnectionsTotal int
54-
// MaxReconnectionsTime is the time period during which the number of
55-
// reconnections must be less than MaxReconnectionsTotal to allow a
56-
// reconnection atttempt.
57-
MaxReconnectionsTime time.Duration
58-
dialer websocket.Dialer
59-
ws *websocket.Conn
60-
url url.URL
61-
header http.Header
62-
dialMessage interface{}
63-
ticker time.Ticker
64-
mu sync.Mutex
65-
reconnections int
66-
isDialed bool
67-
isConnected bool
68-
stop chan bool
48+
dialer websocket.Dialer
49+
ws *websocket.Conn
50+
url url.URL
51+
header http.Header
52+
dialMessage interface{}
53+
ticker time.Ticker
54+
mu sync.Mutex
55+
isDialed bool
56+
isConnected bool
6957
}
7058

7159
// NewConn creates a new Conn with default values.
7260
func NewConn() *Conn {
7361
c := &Conn{
74-
InitialInterval: static.BackoffInitialInterval,
75-
RandomizationFactor: static.BackoffRandomizationFactor,
76-
Multiplier: static.BackoffMultiplier,
77-
MaxInterval: static.BackoffMaxInterval,
78-
MaxElapsedTime: static.BackoffMaxElapsedTime,
79-
MaxReconnectionsTotal: static.MaxReconnectionsTotal,
80-
MaxReconnectionsTime: static.MaxReconnectionsTime,
62+
InitialInterval: static.BackoffInitialInterval,
63+
RandomizationFactor: static.BackoffRandomizationFactor,
64+
Multiplier: static.BackoffMultiplier,
65+
MaxInterval: static.BackoffMaxInterval,
66+
MaxElapsedTime: static.BackoffMaxElapsedTime,
8167
}
8268
return c
8369
}
@@ -101,23 +87,9 @@ func (c *Conn) Dial(address string, header http.Header, dialMsg interface{}) err
10187
}
10288
c.url = *u
10389
c.dialMessage = dialMsg
104-
c.stop = make(chan bool)
10590
c.header = header
10691
c.dialer = websocket.Dialer{}
10792
c.isDialed = true
108-
c.ticker = *time.NewTicker(c.MaxReconnectionsTime)
109-
go func(c *Conn) {
110-
for {
111-
select {
112-
case <-c.stop:
113-
c.ticker.Stop()
114-
return
115-
case <-c.ticker.C:
116-
c.resetReconnections()
117-
}
118-
}
119-
}(c)
120-
12193
return c.connect()
12294
}
12395

@@ -129,8 +101,7 @@ func (c *Conn) Dial(address string, header http.Header, dialMsg interface{}) err
129101
// The write will fail under the following conditions:
130102
// 1. The client has not called Dial (ErrNotDialed).
131103
// 2. The connection is disconnected and it was not able to
132-
// reconnect (ErrTooManyReconnects or an internal connection
133-
// error).
104+
// reconnect.
134105
// 3. The write call in the websocket package failed
135106
// (gorilla/websocket error).
136107
func (c *Conn) WriteMessage(messageType int, data interface{}) error {
@@ -160,39 +131,22 @@ func (c *Conn) IsConnected() bool {
160131
return c.isConnected
161132
}
162133

163-
// CanConnect checks whether it is possible to reconnect
164-
// given the recent number of attempts.
165-
func (c *Conn) CanConnect() bool {
166-
c.mu.Lock()
167-
defer c.mu.Unlock()
168-
return c.reconnections < c.MaxReconnectionsTotal
169-
}
170-
171134
// Close closes the network connection and cleans up private
172135
// resources after the connection is done.
173136
func (c *Conn) Close() error {
174137
if c.isDialed {
175138
c.isDialed = false
176-
c.stop <- true
177139
}
178140
return c.close()
179141
}
180142

181-
// resetReconnections sets the number of disconnects followed
182-
// by reconnects.
183-
func (c *Conn) resetReconnections() {
184-
c.mu.Lock()
185-
defer c.mu.Unlock()
186-
c.reconnections = 0
187-
}
188-
189-
// closeAndReconnect calls close and reconnect.
143+
// closeAndReconnect calls close and reconnects.
190144
func (c *Conn) closeAndReconnect() error {
191145
err := c.close()
192146
if err != nil {
193147
return err
194148
}
195-
return c.reconnect()
149+
return c.connect()
196150
}
197151

198152
// close closes the underlying network connection without
@@ -207,18 +161,6 @@ func (c *Conn) close() error {
207161
return nil
208162
}
209163

210-
// reconnect updates the number of reconnections and
211-
// re-establishes the connection.
212-
func (c *Conn) reconnect() error {
213-
if !c.CanConnect() {
214-
return ErrTooManyReconnects
215-
}
216-
c.mu.Lock()
217-
c.reconnections++
218-
c.mu.Unlock()
219-
return c.connect()
220-
}
221-
222164
// connect creates a new client connection and sends the
223165
// registration message.
224166
// In case of failure, it uses an exponential backoff to

connection/connection_test.go

Lines changed: 11 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99

1010
"github.com/gorilla/websocket"
1111
"github.com/m-lab/locate/connection/testdata"
12-
"github.com/m-lab/locate/static"
1312
)
1413

1514
func Test_Dial(t *testing.T) {
@@ -172,78 +171,32 @@ func Test_WriteMessage_ErrNotDailed(t *testing.T) {
172171
}
173172
}
174173

175-
func Test_WriteMessage_ErrTooManyReconnects(t *testing.T) {
174+
func TestWriteMessage_ClosedServer(t *testing.T) {
176175
c := NewConn()
177-
c.MaxReconnectionsTotal = 0
178176
defer c.Close()
179177
fh := testdata.FakeHandler{}
180178
s := testdata.FakeServer(fh.Upgrade)
181-
defer s.Close()
179+
c.InitialInterval = 500 * time.Millisecond
180+
c.MaxElapsedTime = time.Second
182181
c.Dial(s.URL, http.Header{}, testdata.FakeRegistration)
183-
// Close connection so writes fail.
184-
fh.Close()
185-
186-
// This should return ErrTooManyReconnects because IsConnected is false
187-
// and MaxReconnectionsTotal = 0.
188-
err := c.WriteMessage(websocket.TextMessage, []byte("Health message!"))
189-
if !errors.Is(err, ErrTooManyReconnects) {
190-
t.Errorf("WriteMessage() incorrect error; got: %v, want: ErrTooManyReconnects", err)
191-
}
192182

193-
// Shut server down so reconnection fails.
183+
// Shut down server for testing.
184+
fh.Close()
194185
s.Close()
195-
// Allow reconnections again.
196-
c.MaxReconnectionsTotal = 1
197-
c.MaxElapsedTime = 1 * time.Second
198-
// Should still get an error, but it should not be ErrTooManyReconnects.
199-
err = c.WriteMessage(websocket.TextMessage, []byte("Health message!"))
200-
if err == nil || errors.Is(err, ErrTooManyReconnects) {
201-
t.Errorf("WriteMessage() incorrect error; got: %v, want: !ErrTooManyReconnects", err)
202-
}
203-
}
204186

205-
func Test_CloseAndReconnect(t *testing.T) {
206-
c := NewConn()
207-
fh := testdata.FakeHandler{}
208-
s := testdata.FakeServer(fh.Upgrade)
209-
defer close(c, s)
210-
// For testing, make this time window smaller.
211-
c.MaxReconnectionsTime = time.Second
212-
c.Dial(s.URL, http.Header{}, testdata.FakeRegistration)
213-
214-
for i := 0; i < static.MaxReconnectionsTotal; i++ {
215-
fh.Close()
216-
217-
err := c.WriteMessage(websocket.TextMessage, []byte("Health message!"))
218-
if err != nil {
219-
t.Errorf("WriteMessage() should succeed after reconnection; err: %v", err)
220-
}
221-
222-
if !c.IsConnected() {
223-
t.Error("WriteMessage() failed to reconnect")
224-
}
225-
}
226-
227-
// It should not reconnect once it reaches the maximum number of attempts.
228-
fh.Close()
187+
// Write should fail and connection should become disconnected.
229188
err := c.WriteMessage(websocket.TextMessage, []byte("Health message!"))
230189
if err == nil {
231-
t.Error("WriteMessage() should fail while disconnected")
190+
t.Error("WriteMessage() should fail after server is disconnected")
232191
}
233192
if c.IsConnected() {
234-
t.Error("WriteMessage() reconnection should fail after MaxReconnectionsTotal reached")
193+
t.Errorf("IsConnected() should be false after writing to closed server.")
235194
}
236195

237-
// It should reconnect again after calling WriteMessage once the number of reconnections
238-
// is reset.
239-
timer := time.NewTimer(2 * c.MaxReconnectionsTime)
240-
<-timer.C
196+
// Subsequent writes should fail.
241197
err = c.WriteMessage(websocket.TextMessage, []byte("Health message!"))
242-
if err != nil {
243-
t.Errorf("WriteMessage() should succeed after MaxReconnectionsTime; err: %v", err)
244-
}
245-
if !c.IsConnected() {
246-
t.Error("WriteMessage() failed to reconnect after MaxReconnectionsTime")
198+
if err == nil {
199+
t.Error("WriteMessage() should fail after client detects disconnection")
247200
}
248201
}
249202

static/configs.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@ const (
1818
BackoffInitialInterval = time.Second
1919
BackoffRandomizationFactor = 0.5
2020
BackoffMultiplier = 2
21-
BackoffMaxInterval = time.Hour
21+
BackoffMaxInterval = 5 * time.Minute
2222
BackoffMaxElapsedTime = 0
23-
MaxReconnectionsTotal = 10
24-
MaxReconnectionsTime = time.Hour
2523
HeartbeatPeriod = 10 * time.Second
2624
MemorystoreExportPeriod = 10 * time.Second
2725
PrometheusCheckPeriod = time.Minute

0 commit comments

Comments
 (0)