Merge lp:~chipaca/ubuntu-push/client-session-redialer-mash into lp:ubuntu-push

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: 56
Merged at revision: 54
Proposed branch: lp:~chipaca/ubuntu-push/client-session-redialer-mash
Merge into: lp:ubuntu-push
Diff against target: 709 lines (+267/-160)
10 files modified
bus/connectivity/connectivity.go (+2/-1)
bus/endpoint.go (+0/-7)
bus/testing/testing_endpoint.go (+0/-3)
bus/testing/testing_endpoint_test.go (+0/-6)
client/client.go (+21/-48)
client/client_test.go (+0/-7)
client/session/session.go (+34/-1)
client/session/session_test.go (+76/-1)
util/redialer.go (+71/-36)
util/redialer_test.go (+63/-50)
To merge this branch: bzr merge lp:~chipaca/ubuntu-push/client-session-redialer-mash
Reviewer Review Type Date Requested Status
Samuele Pedroni Approve
Review via email: mp+204813@code.launchpad.net

Commit message

Reworked or refactored util/redialer, and a bit of client/session, and client.

Description of the change

Reworked and/or refactored util/redialer, so i could refactor session, so i could fix client. Or something like that anyway.

To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) wrote :

So, in this branch:
  * reworked redialer:
    * split Dialer into Dialer and Jitterer; only Jitterers are
      jittered. Also removed String from the Dialer interface.
    * moved the jitter default implementation to be the session's;
      moved the test
    * got rid of the AutoRedial/AutoRetry functions; always create an
      AutoRedialer (via NewAutoRedialer) and use it.
    * got rid of quitRedialing, because
    * gave AutoRedialers a Stop() method that would do the right
      thing.
  * session:
    * added AutoRedial, that sets up and launches an AutoRedialer on
      itself.
    * Close() now stops the AutoRedialer
  * client & session:
    * connectSession is now session.AutoRedial; got rid of the extra
      channel needed to be able to stop it. Renamed the channel used
      to communicate with the autoredialer for clarity.
    * disconnectSession is now session.Close()

Revision history for this message
Samuele Pedroni (pedronis) wrote :

636 +func (s *RedialerSuite) TestTwoStops(c *C) {
637 + endp := testibus.NewTestingEndpoint(condition.Work(false), nil)
638 + countCh := make(chan uint32)
639 + ar := NewAutoRedialer(endp)
640 + go func() { countCh <- ar.Redial() }()
641 + ar.Stop()
642 + ar.Stop()
643 }

misses some Checks? otherwise it could be simply folded in the previous test

Revision history for this message
Samuele Pedroni (pedronis) wrote :

260 + sess.retrier.Stop()

there is no test that fails if I comment out that line

Revision history for this message
Samuele Pedroni (pedronis) wrote :

254 + sess.retrier.Stop()

should this be a Close perhaps? also no test fails if I comment that out

Revision history for this message
Samuele Pedroni (pedronis) wrote :

otherwise looks very much an improvement

Revision history for this message
Samuele Pedroni (pedronis) wrote :

ah, looking at make coverage-html output:

func (sess *ClientSession) AutoRedial(doneCh chan uint32) {
        // sess.retrier.Stop()
        sess.retrier = util.NewAutoRedialer(sess)
        go func() { doneCh <- sess.retrier.Redial() }()
}

seems not to be covered at all by tests in client/session

Revision history for this message
John Lenton (chipaca) wrote :

TestTwoStops was just checking that the second Stop() didn't panic nor block; I've folded it into the previous test as you suggested, because there the state after the first Stop() is ensured by the select (I'd have to copy the test all over again, otherwise, really).

54. By John Lenton

made AutoRedialer an interface

55. By John Lenton

tested the closing of the retrier by session.Close and session.AutoRedial

Revision history for this message
John Lenton (chipaca) wrote :

Added tests for AutoRedial, and for stopping the retrier. Coverage now at 100%. Huzzah.

56. By John Lenton

stopRedial

Revision history for this message
Samuele Pedroni (pedronis) wrote :

goody good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bus/connectivity/connectivity.go'
2--- bus/connectivity/connectivity.go 2014-01-28 22:18:39 +0000
3+++ bus/connectivity/connectivity.go 2014-02-05 13:24:10 +0000
4@@ -64,7 +64,8 @@
5 func (cs *connectedState) start() networkmanager.State {
6 var initial networkmanager.State
7 for {
8- cs.connAttempts += util.AutoRedial(cs.endp)
9+ ar := util.NewAutoRedialer(cs.endp)
10+ cs.connAttempts += ar.Redial()
11 nm := networkmanager.New(cs.endp, cs.log)
12
13 // Get the current state.
14
15=== modified file 'bus/endpoint.go'
16--- bus/endpoint.go 2014-01-27 12:41:54 +0000
17+++ bus/endpoint.go 2014-02-05 13:24:10 +0000
18@@ -22,7 +22,6 @@
19 "fmt"
20 "launchpad.net/go-dbus/v1"
21 "launchpad.net/ubuntu-push/logger"
22- "time"
23 )
24
25 /*****************************************************************
26@@ -37,7 +36,6 @@
27 Dial() error
28 Close()
29 String() string
30- Jitter(time.Duration) time.Duration
31 }
32
33 type endpoint struct {
34@@ -137,11 +135,6 @@
35 return fmt.Sprintf("<Connection to %s %#v>", endp.bus, endp.addr)
36 }
37
38-// Jitter() returns 0: no need to jitter D-Bus connections.
39-func (endp *endpoint) Jitter(_ time.Duration) time.Duration {
40- return 0
41-}
42-
43 /*
44 private methods
45 */
46
47=== modified file 'bus/testing/testing_endpoint.go'
48--- bus/testing/testing_endpoint.go 2014-02-03 20:17:14 +0000
49+++ bus/testing/testing_endpoint.go 2014-02-05 13:24:10 +0000
50@@ -135,8 +135,5 @@
51 // see Endpoint's Close. This one does nothing.
52 func (tc *testingEndpoint) Close() {}
53
54-// see Endpoint's Jitter.
55-func (tc *testingEndpoint) Jitter(_ time.Duration) time.Duration { return 0 }
56-
57 // ensure testingEndpoint implements bus.Endpoint
58 var _ bus.Endpoint = &testingEndpoint{}
59
60=== modified file 'bus/testing/testing_endpoint_test.go'
61--- bus/testing/testing_endpoint_test.go 2014-02-03 20:17:14 +0000
62+++ bus/testing/testing_endpoint_test.go 2014-02-05 13:24:10 +0000
63@@ -171,9 +171,3 @@
64 endp := NewTestingEndpoint(condition.Fail2Work(2), nil, "hello there")
65 c.Check(endp.String(), Matches, ".*Still Broken.*hello there.*")
66 }
67-
68-// Test testingEndpoints have no jitters
69-func (s *TestingBusSuite) TestEndpointJitter(c *C) {
70- endp := NewTestingEndpoint(nil, nil)
71- c.Check(endp.Jitter(time.Duration(42)), Equals, time.Duration(0))
72-}
73
74=== modified file 'client/client.go'
75--- client/client.go 2014-02-04 18:19:14 +0000
76+++ client/client.go 2014-02-05 13:24:10 +0000
77@@ -51,20 +51,19 @@
78
79 // Client is the Ubuntu Push Notifications client-side daemon.
80 type Client struct {
81- config ClientConfig
82- log logger.Logger
83- pem []byte
84- idder identifier.Id
85- deviceId string
86- notificationsEndp bus.Endpoint
87- urlDispatcherEndp bus.Endpoint
88- connectivityEndp bus.Endpoint
89- connCh chan bool
90- hasConnectivity bool
91- actionsCh <-chan notifications.RawActionReply
92- session *session.ClientSession
93- sessionRetrierStopper chan bool
94- sessionRetryCh chan uint32
95+ config ClientConfig
96+ log logger.Logger
97+ pem []byte
98+ idder identifier.Id
99+ deviceId string
100+ notificationsEndp bus.Endpoint
101+ urlDispatcherEndp bus.Endpoint
102+ connectivityEndp bus.Endpoint
103+ connCh chan bool
104+ hasConnectivity bool
105+ actionsCh <-chan notifications.RawActionReply
106+ session *session.ClientSession
107+ sessionConnectedCh chan uint32
108 }
109
110 // Configure loads the configuration specified in configPath, and sets it up.
111@@ -86,8 +85,8 @@
112 client.urlDispatcherEndp = bus.SessionBus.Endpoint(urldispatcher.BusAddress, client.log)
113 client.connectivityEndp = bus.SystemBus.Endpoint(networkmanager.BusAddress, client.log)
114
115- client.connCh = make(chan bool)
116- client.sessionRetryCh = make(chan uint32)
117+ client.connCh = make(chan bool, 1)
118+ client.sessionConnectedCh = make(chan uint32, 1)
119
120 if client.config.CertPEMFile != "" {
121 client.pem, err = ioutil.ReadFile(client.config.CertPEMFile)
122@@ -119,8 +118,8 @@
123 go connectivity.ConnectedState(client.connectivityEndp,
124 client.config.ConnectivityConfig, client.log, client.connCh)
125 iniCh := make(chan uint32)
126- go func() { iniCh <- util.AutoRedial(client.notificationsEndp) }()
127- go func() { iniCh <- util.AutoRedial(client.urlDispatcherEndp) }()
128+ go func() { iniCh <- util.NewAutoRedialer(client.notificationsEndp).Redial() }()
129+ go func() { iniCh <- util.NewAutoRedialer(client.urlDispatcherEndp).Redial() }()
130 <-iniCh
131 <-iniCh
132
133@@ -140,32 +139,6 @@
134 return nil
135 }
136
137-// connectSession kicks off the session connection dance
138-func (client *Client) connectSession() {
139- // XXX: lp:1276199
140- if client.sessionRetrierStopper != nil {
141- client.sessionRetrierStopper <- true
142- client.sessionRetrierStopper = nil
143- }
144- ar := &util.AutoRetrier{
145- make(chan bool, 1),
146- client.session.Dial,
147- util.Jitter}
148- client.sessionRetrierStopper = ar.Stop
149- go func() { client.sessionRetryCh <- ar.Retry() }()
150-}
151-
152-// disconnectSession disconnects the session
153-func (client *Client) disconnectSession() {
154- // XXX: lp:1276199
155- if client.sessionRetrierStopper != nil {
156- client.sessionRetrierStopper <- true
157- client.sessionRetrierStopper = nil
158- } else {
159- client.session.Close()
160- }
161-}
162-
163 // handleConnState deals with connectivity events
164 func (client *Client) handleConnState(hasConnectivity bool) {
165 if client.hasConnectivity == hasConnectivity {
166@@ -174,9 +147,9 @@
167 }
168 client.hasConnectivity = hasConnectivity
169 if hasConnectivity {
170- client.connectSession()
171+ client.session.AutoRedial(client.sessionConnectedCh)
172 } else {
173- client.disconnectSession()
174+ client.session.Close()
175 }
176 }
177
178@@ -185,7 +158,7 @@
179 // if we're not connected, we don't really care
180 client.log.Errorf("session exited: %s", err)
181 if client.hasConnectivity {
182- client.connectSession()
183+ client.session.AutoRedial(client.sessionConnectedCh)
184 }
185 }
186
187@@ -232,7 +205,7 @@
188 notifhandler()
189 case err := <-client.session.ErrCh:
190 errhandler(err)
191- case count := <-client.sessionRetryCh:
192+ case count := <-client.sessionConnectedCh:
193 client.log.Debugf("Session connected after %d attempts", count)
194 }
195 }
196
197=== modified file 'client/client_test.go'
198--- client/client_test.go 2014-02-04 13:39:06 +0000
199+++ client/client_test.go 2014-02-05 13:24:10 +0000
200@@ -312,14 +312,9 @@
201 cli := new(Client)
202 cli.log = debuglog
203 cli.initSession()
204- // let's pretend the client had a previous attempt at connecting still pending
205- // (hard to trigger in real life, but possible)
206- ch := make(chan bool, 1)
207- cli.sessionRetrierStopper = ch
208
209 c.Assert(cli.hasConnectivity, Equals, false)
210 cli.handleConnState(true)
211- c.Check(len(ch), Equals, 1)
212 c.Check(cli.hasConnectivity, Equals, true)
213 c.Assert(cli.session, NotNil)
214 }
215@@ -355,12 +350,10 @@
216 cli := new(Client)
217 cli.log = debuglog
218 cli.session, _ = session.NewSession(string(cli.config.Addr), cli.pem, cli.config.ExchangeTimeout.Duration, cli.deviceId, debuglog)
219- cli.sessionRetrierStopper = make(chan bool, 1)
220 cli.hasConnectivity = true
221
222 cli.handleConnState(false)
223 c.Check(cli.session.State(), Equals, session.Disconnected)
224- c.Check(cli.sessionRetrierStopper, IsNil)
225 }
226
227 /*****************************************************************
228
229=== modified file 'client/session/session.go'
230--- client/session/session.go 2014-02-04 13:10:18 +0000
231+++ client/session/session.go 2014-02-05 13:24:10 +0000
232@@ -26,6 +26,8 @@
233 "launchpad.net/ubuntu-push/client/session/levelmap"
234 "launchpad.net/ubuntu-push/logger"
235 "launchpad.net/ubuntu-push/protocol"
236+ "launchpad.net/ubuntu-push/util"
237+ "math/rand"
238 "net"
239 "sync/atomic"
240 "time"
241@@ -68,6 +70,7 @@
242 TLS *tls.Config
243 proto protocol.Protocol
244 pingInterval time.Duration
245+ retrier util.AutoRedialer
246 // status
247 stateP *uint32
248 ErrCh chan error
249@@ -119,7 +122,24 @@
250 return nil
251 }
252
253+func (sess *ClientSession) stopRedial() {
254+ if sess.retrier != nil {
255+ sess.retrier.Stop()
256+ sess.retrier = nil
257+ }
258+}
259+
260+func (sess *ClientSession) AutoRedial(doneCh chan uint32) {
261+ sess.stopRedial()
262+ sess.retrier = util.NewAutoRedialer(sess)
263+ go func() { doneCh <- sess.retrier.Redial() }()
264+}
265+
266 func (sess *ClientSession) Close() {
267+ sess.stopRedial()
268+ sess.doClose()
269+}
270+func (sess *ClientSession) doClose() {
271 if sess.Connection != nil {
272 sess.Connection.Close()
273 // we ignore Close errors, on purpose (the thinking being that
274@@ -258,6 +278,15 @@
275 return err
276 }
277
278+// This Jitter returns a random time.Duration somewhere in [-spread, spread].
279+func (sess *ClientSession) Jitter(spread time.Duration) time.Duration {
280+ if spread < 0 {
281+ panic("spread must be non-negative")
282+ }
283+ n := int64(spread)
284+ return time.Duration(rand.Int63n(2*n+1) - n)
285+}
286+
287 // Dial takes the session from newly created (or newly disconnected)
288 // to running the main loop.
289 func (sess *ClientSession) Dial() error {
290@@ -267,5 +296,9 @@
291 // keep on trying.
292 panic("can't Dial() without a protocol constructor.")
293 }
294- return sess.run(sess.Close, sess.connect, sess.start, sess.loop)
295+ return sess.run(sess.doClose, sess.connect, sess.start, sess.loop)
296+}
297+
298+func init() {
299+ rand.Seed(time.Now().Unix()) // good enough for us (we're not using it for crypto)
300 }
301
302=== modified file 'client/session/session_test.go'
303--- client/session/session_test.go 2014-02-04 13:02:38 +0000
304+++ client/session/session_test.go 2014-02-05 13:24:10 +0000
305@@ -256,6 +256,50 @@
306 c.Check(sess.State(), Equals, Disconnected)
307 }
308
309+type derp struct{ stopped bool }
310+
311+func (*derp) Redial() uint32 { return 0 }
312+func (d *derp) Stop() { d.stopped = true }
313+
314+func (cs *clientSessionSuite) TestCloseStopsRetrier(c *C) {
315+ sess, err := NewSession("", nil, 0, "wah", debuglog)
316+ c.Assert(err, IsNil)
317+ ar := new(derp)
318+ sess.retrier = ar
319+ c.Check(ar.stopped, Equals, false)
320+ sess.Close()
321+ c.Check(ar.stopped, Equals, true)
322+ sess.Close() // double close check
323+ c.Check(ar.stopped, Equals, true)
324+}
325+
326+/****************************************************************
327+ AutoRedial() tests
328+****************************************************************/
329+
330+func (cs *clientSessionSuite) TestAutoRedialWorks(c *C) {
331+ // checks that AutoRedial sets up a retrier and tries redialing it
332+ sess, err := NewSession("", nil, 0, "wah", debuglog)
333+ c.Assert(err, IsNil)
334+ ar := new(derp)
335+ sess.retrier = ar
336+ c.Check(ar.stopped, Equals, false)
337+ sess.AutoRedial(nil)
338+ c.Check(ar.stopped, Equals, true)
339+}
340+
341+func (cs *clientSessionSuite) TestAutoRedialStopsRetrier(c *C) {
342+ // checks that AutoRedial stops the previous retrier
343+ sess, err := NewSession("", nil, 0, "wah", debuglog)
344+ c.Assert(err, IsNil)
345+ ch := make(chan uint32)
346+ c.Check(sess.retrier, IsNil)
347+ sess.AutoRedial(ch)
348+ c.Assert(sess.retrier, NotNil)
349+ sess.retrier.Stop()
350+ c.Check(<-ch, Not(Equals), 0)
351+}
352+
353 /****************************************************************
354 handlePing() tests
355 ****************************************************************/
356@@ -624,12 +668,43 @@
357 }
358
359 /****************************************************************
360+ Jitter() tests
361+****************************************************************/
362+
363+func (s *clientSessionSuite) TestJitter(c *C) {
364+ sess, err := NewSession("", nil, 0, "wah", helpers.NewTestLogger(c, "debug"))
365+ c.Assert(err, IsNil)
366+ num_tries := 20 // should do the math
367+ spread := time.Second //
368+ has_neg := false
369+ has_pos := false
370+ has_zero := true
371+ for i := 0; i < num_tries; i++ {
372+ n := sess.Jitter(spread)
373+ if n > 0 {
374+ has_pos = true
375+ } else if n < 0 {
376+ has_neg = true
377+ } else {
378+ has_zero = true
379+ }
380+ }
381+ c.Check(has_neg, Equals, true)
382+ c.Check(has_pos, Equals, true)
383+ c.Check(has_zero, Equals, true)
384+
385+ // a negative spread is caught in the reasonable place
386+ c.Check(func() { sess.Jitter(time.Duration(-1)) }, PanicMatches,
387+ "spread must be non-negative")
388+}
389+
390+/****************************************************************
391 Dial() tests
392 ****************************************************************/
393
394 func (cs *clientSessionSuite) TestDialPanics(c *C) {
395 // one last unhappy test
396- sess, err := NewSession("", nil, 0, "wah", debuglog)
397+ sess, err := NewSession("", nil, 0, "wah", helpers.NewTestLogger(c, "debug"))
398 c.Assert(err, IsNil)
399 sess.Protocolator = nil
400 c.Check(sess.Dial, PanicMatches, ".*protocol constructor.")
401
402=== modified file 'util/redialer.go'
403--- util/redialer.go 2014-02-04 13:02:38 +0000
404+++ util/redialer.go 2014-02-05 13:24:10 +0000
405@@ -17,7 +17,6 @@
406 package util
407
408 import (
409- "math/rand"
410 "sync"
411 "time"
412 )
413@@ -27,7 +26,12 @@
414 // fails.
415 type Dialer interface {
416 Dial() error
417- String() string
418+}
419+
420+// A Jitterer is a Dialer that wants to vary the backoff a little (to avoid a
421+// thundering herd, for example).
422+type Jitterer interface {
423+ Dialer
424 Jitter(time.Duration) time.Duration
425 }
426
427@@ -35,17 +39,16 @@
428 var timeouts []time.Duration
429 var trwlock sync.RWMutex
430
431-var ( // for use in testing
432- quitRedialing chan bool = make(chan bool)
433-)
434-
435+// retrieve the list of timeouts used for exponential backoff
436 func Timeouts() []time.Duration {
437 trwlock.RLock()
438 defer trwlock.RUnlock()
439 return timeouts
440 }
441
442-// for testing
443+// for testing: change the default timeouts for the provided ones,
444+// returning the defaults (the idea being you reset them on test
445+// teardown)
446 func SwapTimeouts(newTimeouts []time.Duration) (oldTimeouts []time.Duration) {
447 trwlock.Lock()
448 defer trwlock.Unlock()
449@@ -53,32 +56,60 @@
450 return
451 }
452
453-// Jitter returns a random time.Duration somewhere in [-spread, spread].
454-//
455-// This is meant as a default implementation for Dialers to use if wanted.
456-func Jitter(spread time.Duration) time.Duration {
457- if spread < 0 {
458- panic("spread must be non-negative")
459+// An AutoRedialer's Redial() method retries its dialer's Dial() method until
460+// it stops returning an error. It does exponential (optionally jitter'ed)
461+// backoff.
462+type AutoRedialer interface {
463+ Redial() uint32 // Redial keeps on calling Dial until it stops returning an error.
464+ Stop() // Stop shuts down the given AutoRedialer, if it is still retrying.
465+}
466+
467+type autoRedialer struct {
468+ stop chan bool
469+ lock sync.RWMutex
470+ dial func() error
471+ jitter func(time.Duration) time.Duration
472+}
473+
474+func (ar *autoRedialer) Stop() {
475+ if ar != nil {
476+ ar.lock.RLock()
477+ defer ar.lock.RUnlock()
478+ if ar.stop != nil {
479+ ar.stop <- true
480+ }
481 }
482- n := int64(spread)
483- return time.Duration(rand.Int63n(2*n+1) - n)
484-}
485-
486-type AutoRetrier struct {
487- Stop chan bool
488- Dial func() error
489- Jitter func(time.Duration) time.Duration
490-}
491-
492-// AutoRetry keeps on calling Dial until it stops returning an error. It does
493+}
494+
495+func (ar *autoRedialer) shutdown() {
496+ ar.lock.Lock()
497+ defer ar.lock.Unlock()
498+ close(ar.stop)
499+ ar.stop = nil
500+}
501+
502+// Redial keeps on calling Dial until it stops returning an error. It does
503 // exponential backoff, adding the output of Jitter at each step back.
504-func (ar *AutoRetrier) Retry() uint32 {
505+func (ar *autoRedialer) Redial() uint32 {
506+ if ar == nil {
507+ // at least it's better than the segfault...
508+ panic("you can't Redial a nil AutoRedialer")
509+ }
510+ if ar.stop == nil {
511+ panic("this AutoRedialer has already been shut down")
512+ }
513+ defer ar.shutdown()
514+
515+ ar.lock.RLock()
516+ stop := ar.stop
517+ ar.lock.RUnlock()
518+
519 var timeout time.Duration
520 var dialAttempts uint32 = 0 // unsigned so it can wrap safely ...
521 timeouts := Timeouts()
522 var numTimeouts uint32 = uint32(len(timeouts))
523 for {
524- if ar.Dial() == nil {
525+ if ar.dial() == nil {
526 return dialAttempts + 1
527 }
528 if dialAttempts < numTimeouts {
529@@ -86,22 +117,27 @@
530 } else {
531 timeout = timeouts[numTimeouts-1]
532 }
533- timeout += ar.Jitter(timeout)
534+ if ar.jitter != nil {
535+ timeout += ar.jitter(timeout)
536+ }
537 dialAttempts++
538 select {
539- case <-ar.Stop:
540+ case <-stop:
541 return dialAttempts
542- case <-time.NewTimer(timeout).C:
543+ case <-time.After(timeout):
544 }
545 }
546 }
547
548-// AutoRedialer takes a Dialer and retries its Dial() method until it
549-// stops returning an error. It does exponential (optionally
550-// jitter'ed) backoff.
551-func AutoRedial(dialer Dialer) uint32 {
552- ar := &AutoRetrier{quitRedialing, dialer.Dial, dialer.Jitter}
553- return ar.Retry()
554+// returns a stoppable AutoRedialer using the provided Dialer. If the Dialer
555+// is also a Jitterer, the backoff will be jittered.
556+func NewAutoRedialer(dialer Dialer) AutoRedialer {
557+ ar := &autoRedialer{stop: make(chan bool), dial: dialer.Dial}
558+ jitterer, ok := dialer.(Jitterer)
559+ if ok {
560+ ar.jitter = jitterer.Jitter
561+ }
562+ return ar
563 }
564
565 func init() {
566@@ -111,5 +147,4 @@
567 timeouts[i] = time.Duration(n) * time.Second
568 }
569 SwapTimeouts(timeouts)
570- rand.Seed(time.Now().Unix()) // good enough for us (not crypto, yadda)
571 }
572
573=== modified file 'util/redialer_test.go'
574--- util/redialer_test.go 2014-02-04 13:02:38 +0000
575+++ util/redialer_test.go 2014-02-05 13:24:10 +0000
576@@ -17,9 +17,9 @@
577 package util
578
579 import (
580- "errors"
581 "io/ioutil"
582 . "launchpad.net/gocheck"
583+ "launchpad.net/ubuntu-push/bus"
584 testibus "launchpad.net/ubuntu-push/bus/testing"
585 "launchpad.net/ubuntu-push/logger"
586 "launchpad.net/ubuntu-push/testing/condition"
587@@ -46,60 +46,73 @@
588 s.timeouts = nil
589 }
590
591+// Redial() tests
592+
593 func (s *RedialerSuite) TestWorks(c *C) {
594 endp := testibus.NewTestingEndpoint(condition.Fail2Work(3), nil)
595- // instead of bus.Dial(), we do AutoRedial(bus)
596- c.Check(AutoRedial(endp), Equals, uint32(4))
597-}
598-
599-func (s *RedialerSuite) TestCanBeStopped(c *C) {
600+ ar := NewAutoRedialer(endp)
601+ c.Check(ar.(*autoRedialer).stop, NotNil)
602+ c.Check(ar.Redial(), Equals, uint32(4))
603+ // and on success, the stopper goes away
604+ c.Check(ar.(*autoRedialer).stop, IsNil)
605+}
606+
607+func (s *RedialerSuite) TestRetryNil(c *C) {
608+ var ar *autoRedialer
609+ c.Check(ar.Redial, Not(PanicMatches), ".* nil pointer dereference")
610+}
611+
612+func (s *RedialerSuite) TestRetryTwice(c *C) {
613+ endp := testibus.NewTestingEndpoint(condition.Work(true), nil)
614+ ar := NewAutoRedialer(endp)
615+ c.Check(ar.Redial(), Equals, uint32(1))
616+ c.Check(ar.Redial, PanicMatches, ".*shut.?down.*")
617+}
618+
619+type JitteringEndpoint struct {
620+ bus.Endpoint
621+ jittered int
622+}
623+
624+func (j *JitteringEndpoint) Jitter(time.Duration) time.Duration {
625+ j.jittered++
626+ return 0
627+}
628+
629+func (s *RedialerSuite) TestJitterWorks(c *C) {
630+ endp := &JitteringEndpoint{
631+ testibus.NewTestingEndpoint(condition.Fail2Work(3), nil),
632+ 0,
633+ }
634+ ar := NewAutoRedialer(endp)
635+ c.Check(ar.Redial(), Equals, uint32(4))
636+ c.Check(endp.jittered, Equals, 3)
637+}
638+
639+// Stop() tests
640+
641+func (s *RedialerSuite) TestStopWorksOnNil(c *C) {
642+ // as a convenience, Stop() should succeed on nil
643+ // (a nil retrier certainly isn't retrying!)
644+ var ar *autoRedialer
645+ c.Check(ar, IsNil)
646+ ar.Stop() // nothing happens
647+}
648+
649+func (s *RedialerSuite) TestStopStops(c *C) {
650 endp := testibus.NewTestingEndpoint(condition.Work(false), nil)
651- ch := make(chan uint32)
652- go func() { ch <- AutoRedial(endp) }()
653- quitRedialing <- true
654+ countCh := make(chan uint32)
655+ ar := NewAutoRedialer(endp)
656+ go func() { countCh <- ar.Redial() }()
657+ ar.Stop()
658 select {
659- case n := <-ch:
660+ case n := <-countCh:
661 c.Check(n, Equals, uint32(1))
662- case <-time.Tick(20 * time.Millisecond):
663+ case <-time.After(20 * time.Millisecond):
664 c.Fatal("timed out waiting for redial")
665 }
666-}
667-
668-func (s *RedialerSuite) TestAutoRetry(c *C) {
669- cond := condition.Fail2Work(5)
670- f := func() error {
671- if cond.OK() {
672- return nil
673- } else {
674- return errors.New("X")
675- }
676- }
677- jitter := func(time.Duration) time.Duration { return 0 }
678- ar := &AutoRetrier{nil, f, jitter}
679- c.Check(ar.Retry(), Equals, uint32(6))
680-}
681-
682-func (s *RedialerSuite) TestJitter(c *C) {
683- num_tries := 20 // should do the math
684- spread := time.Second //
685- has_neg := false
686- has_pos := false
687- has_zero := true
688- for i := 0; i < num_tries; i++ {
689- n := Jitter(spread)
690- if n > 0 {
691- has_pos = true
692- } else if n < 0 {
693- has_neg = true
694- } else {
695- has_zero = true
696- }
697- }
698- c.Check(has_neg, Equals, true)
699- c.Check(has_pos, Equals, true)
700- c.Check(has_zero, Equals, true)
701-
702- // a negative spread is caught in the reasonable place
703- c.Check(func() { Jitter(time.Duration(-1)) }, PanicMatches,
704- "spread must be non-negative")
705+ // on Stop(), the stopper goes away too
706+ c.Check(ar.(*autoRedialer).stop, IsNil)
707+ // and the next Stop() doesn't panic nor block
708+ ar.Stop()
709 }

Subscribers

People subscribed via source and target branches