Merge lp:~chipaca/ubuntu-push/no-more-session-autoredial into lp:ubuntu-push/automatic
- no-more-session-autoredial
- Merge into automatic
Proposed by
John Lenton
Status: | Merged |
---|---|
Approved by: | John Lenton |
Approved revision: | 381 |
Merged at revision: | 375 |
Proposed branch: | lp:~chipaca/ubuntu-push/no-more-session-autoredial |
Merge into: | lp:ubuntu-push/automatic |
Prerequisite: | lp:~chipaca/ubuntu-push/session-state-lock |
Diff against target: |
1169 lines (+401/-196) 4 files modified
client/client.go (+4/-26) client/client_test.go (+30/-109) client/session/session.go (+106/-7) client/session/session_test.go (+261/-54) |
To merge this branch: | bzr merge lp:~chipaca/ubuntu-push/no-more-session-autoredial |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Samuele Pedroni | Approve | ||
Review via email: mp+252612@code.launchpad.net |
Commit message
Remove AutoRedial from ClientSession's interface.
Description of the change
To post a comment you must log in.
- 378. By John Lenton
-
removed spurious test
- 379. By John Lenton
-
merged from lp
Revision history for this message
Samuele Pedroni (pedronis) wrote : | # |
Revision history for this message
John Lenton (chipaca) wrote : | # |
Agreed on both points, fixing.
- 380. By John Lenton
-
call close from stopCh handler; set state to shutdown before closing stopCh.
- 381. By John Lenton
-
repeat myself inside the stopCh handler, because it's not *exactly* doClose what I want, and there isn't yet a clear way of generalizing that function without making a mess down-pipe.
Revision history for this message
Samuele Pedroni (pedronis) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'client/client.go' |
2 | --- client/client.go 2015-03-03 10:46:04 +0000 |
3 | +++ client/client.go 2015-03-17 16:47:16 +0000 |
4 | @@ -115,7 +115,6 @@ |
5 | systemImageEndp bus.Endpoint |
6 | systemImageInfo *systemimage.InfoResult |
7 | connCh chan bool |
8 | - hasConnectivity bool |
9 | session session.ClientSession |
10 | sessionConnectedCh chan uint32 |
11 | pushService PushService |
12 | @@ -126,7 +125,6 @@ |
13 | poller poller.Poller |
14 | accountsCh <-chan accounts.Changed |
15 | // session-side channels |
16 | - errCh chan error |
17 | broadcastCh chan *session.BroadcastNotification |
18 | notificationsCh chan session.AddressedNotification |
19 | } |
20 | @@ -137,7 +135,6 @@ |
21 | return &PushClient{ |
22 | configPath: configPath, |
23 | leveldbPath: leveldbPath, |
24 | - errCh: make(chan error), |
25 | broadcastCh: make(chan *session.BroadcastNotification), |
26 | notificationsCh: make(chan session.AddressedNotification), |
27 | } |
28 | @@ -212,7 +209,6 @@ |
29 | AuthGetter: client.getAuthorization, |
30 | AuthURL: client.config.SessionURL, |
31 | AddresseeChecker: client, |
32 | - ErrCh: client.errCh, |
33 | BroadcastCh: client.broadcastCh, |
34 | NotificationsCh: client.notificationsCh, |
35 | } |
36 | @@ -317,6 +313,7 @@ |
37 | return err |
38 | } |
39 | client.session = sess |
40 | + sess.KeepConnection() |
41 | client.poller = poller.New(client.derivePollerSetup()) |
42 | return nil |
43 | } |
44 | @@ -388,24 +385,8 @@ |
45 | // handleConnState deals with connectivity events |
46 | func (client *PushClient) handleConnState(hasConnectivity bool) { |
47 | client.log.Debugf("handleConnState: %v", hasConnectivity) |
48 | - if client.hasConnectivity == hasConnectivity { |
49 | - // nothing to do! |
50 | - return |
51 | - } |
52 | - client.hasConnectivity = hasConnectivity |
53 | - client.session.Close() |
54 | - if hasConnectivity { |
55 | - client.session.AutoRedial(client.sessionConnectedCh) |
56 | - } |
57 | -} |
58 | - |
59 | -// handleErr deals with the session erroring out of its loop |
60 | -func (client *PushClient) handleErr(err error) { |
61 | - // if we're not connected, we don't really care |
62 | - client.log.Errorf("session exited: %s", err) |
63 | - if client.hasConnectivity { |
64 | - client.session.AutoRedial(client.sessionConnectedCh) |
65 | - } |
66 | + client.session.HasConnectivity(hasConnectivity) |
67 | + client.log.Debugf("handled.") |
68 | } |
69 | |
70 | // filterBroadcastNotification finds out if the notification is about an actual |
71 | @@ -487,7 +468,7 @@ |
72 | } |
73 | |
74 | // doLoop connects events with their handlers |
75 | -func (client *PushClient) doLoop(connhandler func(bool), bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(session.AddressedNotification) error, errhandler func(error), unregisterhandler func(*click.AppId), accountshandler func()) { |
76 | +func (client *PushClient) doLoop(connhandler func(bool), bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(session.AddressedNotification) error, unregisterhandler func(*click.AppId), accountshandler func()) { |
77 | for { |
78 | select { |
79 | case <-client.accountsCh: |
80 | @@ -498,8 +479,6 @@ |
81 | bcasthandler(bcast) |
82 | case aucast := <-client.notificationsCh: |
83 | ucasthandler(aucast) |
84 | - case err := <-client.errCh: |
85 | - errhandler(err) |
86 | case count := <-client.sessionConnectedCh: |
87 | client.log.Debugf("session connected after %d attempts", count) |
88 | case app := <-client.unregisterCh: |
89 | @@ -524,7 +503,6 @@ |
90 | client.doLoop(client.handleConnState, |
91 | client.handleBroadcastNotification, |
92 | client.handleUnicastNotification, |
93 | - client.handleErr, |
94 | client.handleUnregister, |
95 | client.handleAccountsChange, |
96 | ) |
97 | |
98 | === modified file 'client/client_test.go' |
99 | --- client/client_test.go 2015-03-03 10:46:04 +0000 |
100 | +++ client/client_test.go 2015-03-17 16:47:16 +0000 |
101 | @@ -43,7 +43,6 @@ |
102 | clickhelp "launchpad.net/ubuntu-push/click/testing" |
103 | "launchpad.net/ubuntu-push/client/service" |
104 | "launchpad.net/ubuntu-push/client/session" |
105 | - "launchpad.net/ubuntu-push/client/session/seenstate" |
106 | "launchpad.net/ubuntu-push/config" |
107 | "launchpad.net/ubuntu-push/identifier" |
108 | idtesting "launchpad.net/ubuntu-push/identifier/testing" |
109 | @@ -427,7 +426,6 @@ |
110 | AuthGetter: func(string) string { return "" }, |
111 | AuthURL: "xyzzy://", |
112 | AddresseeChecker: cli, |
113 | - ErrCh: make(chan error), |
114 | BroadcastCh: make(chan *session.BroadcastNotification), |
115 | NotificationsCh: make(chan session.AddressedNotification), |
116 | } |
117 | @@ -444,10 +442,8 @@ |
118 | // compare authGetter by string |
119 | c.Check(fmt.Sprintf("%#v", conf.AuthGetter), Equals, fmt.Sprintf("%#v", cli.getAuthorization)) |
120 | // channels are ok as long as non-nil |
121 | - conf.ErrCh = nil |
122 | conf.BroadcastCh = nil |
123 | conf.NotificationsCh = nil |
124 | - expected.ErrCh = nil |
125 | expected.BroadcastCh = nil |
126 | expected.NotificationsCh = nil |
127 | // and set it to nil |
128 | @@ -534,9 +530,11 @@ |
129 | type derivePollerSession struct{} |
130 | |
131 | func (s *derivePollerSession) Close() {} |
132 | -func (s *derivePollerSession) AutoRedial(ch chan uint32) {} |
133 | func (s *derivePollerSession) ClearCookie() {} |
134 | func (s *derivePollerSession) State() session.ClientSessionState { return session.Unknown } |
135 | +func (s *derivePollerSession) HasConnectivity(bool) error { return nil } |
136 | +func (s *derivePollerSession) KeepConnection() error { return nil } |
137 | +func (s *derivePollerSession) StopKeepConnection() {} |
138 | |
139 | func (cs *clientSuite) TestDerivePollerSetup(c *C) { |
140 | cs.writeTestConfig(map[string]interface{}{}) |
141 | @@ -722,22 +720,6 @@ |
142 | } |
143 | |
144 | /***************************************************************** |
145 | - handleErr tests |
146 | -******************************************************************/ |
147 | - |
148 | -func (cs *clientSuite) TestHandleErr(c *C) { |
149 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
150 | - cli.log = cs.log |
151 | - cli.systemImageInfo = siInfoRes |
152 | - c.Assert(cli.initSessionAndPoller(), IsNil) |
153 | - cs.log.ResetCapture() |
154 | - cli.hasConnectivity = true |
155 | - defer cli.session.Close() |
156 | - cli.handleErr(errors.New("bananas")) |
157 | - c.Check(cs.log.Captured(), Matches, ".*session exited.*bananas\n") |
158 | -} |
159 | - |
160 | -/***************************************************************** |
161 | seenStateFactory tests |
162 | ******************************************************************/ |
163 | |
164 | @@ -758,66 +740,6 @@ |
165 | } |
166 | |
167 | /***************************************************************** |
168 | - handleConnState tests |
169 | -******************************************************************/ |
170 | - |
171 | -type handleConnStateSession struct { |
172 | - connected bool |
173 | -} |
174 | - |
175 | -func (s *handleConnStateSession) AutoRedial(ch chan uint32) { s.connected = true } |
176 | -func (s *handleConnStateSession) Close() { s.connected = false } |
177 | -func (s *handleConnStateSession) ClearCookie() {} |
178 | -func (s *handleConnStateSession) State() session.ClientSessionState { return session.Unknown } |
179 | - |
180 | -func (cs *clientSuite) TestHandleConnStateD2C(c *C) { |
181 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
182 | - cli.log = cs.log |
183 | - sess := &handleConnStateSession{connected: false} |
184 | - cli.session = sess |
185 | - |
186 | - c.Assert(cli.hasConnectivity, Equals, false) |
187 | - cli.handleConnState(true) |
188 | - c.Check(cli.hasConnectivity, Equals, true) |
189 | - c.Check(sess.connected, Equals, true) |
190 | -} |
191 | - |
192 | -func (cs *clientSuite) TestHandleConnStateSame(c *C) { |
193 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
194 | - cli.log = cs.log |
195 | - // here we want to check that we don't do anything |
196 | - c.Assert(cli.session, IsNil) |
197 | - c.Assert(cli.hasConnectivity, Equals, false) |
198 | - cli.handleConnState(false) |
199 | - c.Check(cli.session, IsNil) |
200 | - |
201 | - cli.hasConnectivity = true |
202 | - cli.handleConnState(true) |
203 | - c.Check(cli.session, IsNil) |
204 | -} |
205 | - |
206 | -func (cs *clientSuite) TestHandleConnStateC2D(c *C) { |
207 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
208 | - cli.log = cs.log |
209 | - sess := &handleConnStateSession{connected: true} |
210 | - cli.session = sess |
211 | - cli.hasConnectivity = true |
212 | - |
213 | - cli.handleConnState(false) |
214 | - c.Check(sess.connected, Equals, false) |
215 | -} |
216 | - |
217 | -func (cs *clientSuite) TestHandleConnStateC2DPending(c *C) { |
218 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
219 | - cli.log = cs.log |
220 | - cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, seenstate.NewSeenState, cs.log) |
221 | - cli.hasConnectivity = true |
222 | - |
223 | - cli.handleConnState(false) |
224 | - c.Check(cli.session.State(), Equals, session.Disconnected) |
225 | -} |
226 | - |
227 | -/***************************************************************** |
228 | filterBroadcastNotification tests |
229 | ******************************************************************/ |
230 | |
231 | @@ -1035,7 +957,6 @@ |
232 | var nopConn = func(bool) {} |
233 | var nopBcast = func(*session.BroadcastNotification) error { return nil } |
234 | var nopUcast = func(session.AddressedNotification) error { return nil } |
235 | -var nopError = func(error) {} |
236 | var nopUnregister = func(*click.AppId) {} |
237 | var nopAcct = func() {} |
238 | |
239 | @@ -1048,7 +969,7 @@ |
240 | c.Assert(cli.initSessionAndPoller(), IsNil) |
241 | |
242 | ch := make(chan bool, 1) |
243 | - go cli.doLoop(func(bool) { ch <- true }, nopBcast, nopUcast, nopError, nopUnregister, nopAcct) |
244 | + go cli.doLoop(func(bool) { ch <- true }, nopBcast, nopUcast, nopUnregister, nopAcct) |
245 | c.Check(takeNextBool(ch), Equals, true) |
246 | } |
247 | |
248 | @@ -1061,7 +982,7 @@ |
249 | cli.broadcastCh <- &session.BroadcastNotification{} |
250 | |
251 | ch := make(chan bool, 1) |
252 | - go cli.doLoop(nopConn, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopError, nopUnregister, nopAcct) |
253 | + go cli.doLoop(nopConn, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopUnregister, nopAcct) |
254 | c.Check(takeNextBool(ch), Equals, true) |
255 | } |
256 | |
257 | @@ -1074,20 +995,7 @@ |
258 | cli.notificationsCh <- session.AddressedNotification{} |
259 | |
260 | ch := make(chan bool, 1) |
261 | - go cli.doLoop(nopConn, nopBcast, func(session.AddressedNotification) error { ch <- true; return nil }, nopError, nopUnregister, nopAcct) |
262 | - c.Check(takeNextBool(ch), Equals, true) |
263 | -} |
264 | - |
265 | -func (cs *clientSuite) TestDoLoopErr(c *C) { |
266 | - cli := NewPushClient(cs.configPath, cs.leveldbPath) |
267 | - cli.log = cs.log |
268 | - cli.systemImageInfo = siInfoRes |
269 | - c.Assert(cli.initSessionAndPoller(), IsNil) |
270 | - cli.errCh = make(chan error, 1) |
271 | - cli.errCh <- nil |
272 | - |
273 | - ch := make(chan bool, 1) |
274 | - go cli.doLoop(nopConn, nopBcast, nopUcast, func(error) { ch <- true }, nopUnregister, nopAcct) |
275 | + go cli.doLoop(nopConn, nopBcast, func(session.AddressedNotification) error { ch <- true; return nil }, nopUnregister, nopAcct) |
276 | c.Check(takeNextBool(ch), Equals, true) |
277 | } |
278 | |
279 | @@ -1100,7 +1008,7 @@ |
280 | cli.unregisterCh <- app1 |
281 | |
282 | ch := make(chan bool, 1) |
283 | - go cli.doLoop(nopConn, nopBcast, nopUcast, nopError, func(app *click.AppId) { c.Check(app.Original(), Equals, appId1); ch <- true }, nopAcct) |
284 | + go cli.doLoop(nopConn, nopBcast, nopUcast, func(app *click.AppId) { c.Check(app.Original(), Equals, appId1); ch <- true }, nopAcct) |
285 | c.Check(takeNextBool(ch), Equals, true) |
286 | } |
287 | |
288 | @@ -1114,7 +1022,7 @@ |
289 | cli.accountsCh = acctCh |
290 | |
291 | ch := make(chan bool, 1) |
292 | - go cli.doLoop(nopConn, nopBcast, nopUcast, nopError, nopUnregister, func() { ch <- true }) |
293 | + go cli.doLoop(nopConn, nopBcast, nopUcast, nopUnregister, func() { ch <- true }) |
294 | c.Check(takeNextBool(ch), Equals, true) |
295 | } |
296 | |
297 | @@ -1149,6 +1057,21 @@ |
298 | Loop() tests |
299 | ******************************************************************/ |
300 | |
301 | +type loopSession struct{ hasConn bool } |
302 | + |
303 | +func (s *loopSession) Close() {} |
304 | +func (s *loopSession) ClearCookie() {} |
305 | +func (s *loopSession) State() session.ClientSessionState { |
306 | + if s.hasConn { |
307 | + return session.Connected |
308 | + } else { |
309 | + return session.Disconnected |
310 | + } |
311 | +} |
312 | +func (s *loopSession) HasConnectivity(hasConn bool) error { s.hasConn = hasConn; return nil } |
313 | +func (s *loopSession) KeepConnection() error { return nil } |
314 | +func (s *loopSession) StopKeepConnection() {} |
315 | + |
316 | func (cs *clientSuite) TestLoop(c *C) { |
317 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
318 | cli.connCh = make(chan bool) |
319 | @@ -1164,7 +1087,6 @@ |
320 | c.Assert(cli.initSessionAndPoller(), IsNil) |
321 | |
322 | cli.broadcastCh = make(chan *session.BroadcastNotification) |
323 | - cli.errCh = make(chan error) |
324 | |
325 | // we use tick() to make sure things have been through the |
326 | // event loop at least once before looking at things; |
327 | @@ -1179,26 +1101,25 @@ |
328 | tick() |
329 | c.Check(cs.log.Captured(), Matches, "(?msi).*Session connected after 42 attempts$") |
330 | |
331 | + c.Assert(cli.session, NotNil) |
332 | + cli.session.StopKeepConnection() |
333 | + cli.session = &loopSession{} |
334 | + |
335 | // loop() should have connected: |
336 | // * connCh to the connectivity checker |
337 | - c.Check(cli.hasConnectivity, Equals, false) |
338 | + c.Check(cli.session.State(), Equals, session.Disconnected) |
339 | cli.connCh <- true |
340 | tick() |
341 | - c.Check(cli.hasConnectivity, Equals, true) |
342 | + c.Check(cli.session.State(), Equals, session.Connected) |
343 | cli.connCh <- false |
344 | tick() |
345 | - c.Check(cli.hasConnectivity, Equals, false) |
346 | + c.Check(cli.session.State(), Equals, session.Disconnected) |
347 | |
348 | // * session.BroadcastCh to the notifications handler |
349 | c.Check(d.bcastCount, Equals, 0) |
350 | cli.broadcastCh <- positiveBroadcastNotification |
351 | tick() |
352 | c.Check(d.bcastCount, Equals, 1) |
353 | - |
354 | - // * session.ErrCh to the error handler |
355 | - cli.errCh <- nil |
356 | - tick() |
357 | - c.Check(cs.log.Captured(), Matches, "(?ms).*session exited.*") |
358 | } |
359 | |
360 | /***************************************************************** |
361 | |
362 | === modified file 'client/session/session.go' |
363 | --- client/session/session.go 2015-03-12 12:05:40 +0000 |
364 | +++ client/session/session.go 2015-03-17 16:47:16 +0000 |
365 | @@ -70,10 +70,12 @@ |
366 | |
367 | const ( |
368 | Error ClientSessionState = iota |
369 | + Pristine |
370 | Disconnected |
371 | Connected |
372 | Started |
373 | Running |
374 | + Shutdown |
375 | Unknown |
376 | ) |
377 | |
378 | @@ -83,10 +85,12 @@ |
379 | } |
380 | return [Unknown]string{ |
381 | "Error", |
382 | + "Pristine", |
383 | "Disconnected", |
384 | "Connected", |
385 | "Started", |
386 | "Running", |
387 | + "Shutdown", |
388 | }[s] |
389 | } |
390 | |
391 | @@ -118,7 +122,6 @@ |
392 | AuthGetter func(string) string |
393 | AuthURL string |
394 | AddresseeChecker AddresseeChecking |
395 | - ErrCh chan error |
396 | BroadcastCh chan *BroadcastNotification |
397 | NotificationsCh chan AddressedNotification |
398 | } |
399 | @@ -126,9 +129,11 @@ |
400 | // ClientSession holds a client<->server session and its configuration. |
401 | type ClientSession interface { |
402 | Close() |
403 | - AutoRedial(doneCh chan uint32) |
404 | ClearCookie() |
405 | State() ClientSessionState |
406 | + HasConnectivity(bool) error |
407 | + KeepConnection() error |
408 | + StopKeepConnection() |
409 | } |
410 | |
411 | type clientSession struct { |
412 | @@ -169,6 +174,20 @@ |
413 | redialJitter func(time.Duration) time.Duration |
414 | redialDelays []time.Duration |
415 | redialDelaysIdx int |
416 | + // connection events come in over here |
417 | + connCh chan bool |
418 | + // last seen connection event is here |
419 | + lastConn bool |
420 | + // connection events are handled by this |
421 | + connHandler func(bool) |
422 | + // autoredial goes over here (xxx spurious goroutine involved) |
423 | + doneCh chan uint32 |
424 | + // main loop errors out through here (possibly another spurious goroutine) |
425 | + errCh chan error |
426 | + // main loop errors are handled by this |
427 | + errHandler func(error) |
428 | + // look, a stopper! |
429 | + stopCh chan struct{} |
430 | } |
431 | |
432 | func redialDelay(sess *clientSession) time.Duration { |
433 | @@ -207,10 +226,10 @@ |
434 | Protocolator: protocol.NewProtocol0, |
435 | SeenState: seenState, |
436 | TLS: &tls.Config{}, |
437 | - state: Disconnected, |
438 | + state: Pristine, |
439 | timeSince: time.Since, |
440 | shouldDelayP: &shouldDelay, |
441 | - redialDelay: redialDelay, |
442 | + redialDelay: redialDelay, // NOTE there are tests that use calling sess.redialDelay as an indication of calling autoRedial! |
443 | redialDelays: util.Timeouts(), |
444 | } |
445 | sess.redialJitter = sess.Jitter |
446 | @@ -222,6 +241,15 @@ |
447 | } |
448 | sess.TLS.RootCAs = cp |
449 | } |
450 | + sess.doneCh = make(chan uint32, 1) |
451 | + sess.stopCh = make(chan struct{}) |
452 | + sess.connCh = make(chan bool, 1) |
453 | + sess.errCh = make(chan error, 1) |
454 | + |
455 | + // to be overridden by tests |
456 | + sess.connHandler = sess.handleConn |
457 | + sess.errHandler = sess.handleErr |
458 | + |
459 | return sess, nil |
460 | } |
461 | |
462 | @@ -383,7 +411,7 @@ |
463 | } |
464 | } |
465 | |
466 | -func (sess *clientSession) AutoRedial(doneCh chan uint32) { |
467 | +func (sess *clientSession) autoRedial() { |
468 | sess.stopRedial() |
469 | if time.Since(sess.lastAutoRedial) < 2*time.Second { |
470 | sess.setShouldDelay() |
471 | @@ -405,7 +433,7 @@ |
472 | return |
473 | } |
474 | sess.Log.Debugf("session autoredialier launching Redial goroutine") |
475 | - doneCh <- retrier.Redial() |
476 | + sess.doneCh <- retrier.Redial() |
477 | }() |
478 | } |
479 | |
480 | @@ -657,7 +685,7 @@ |
481 | if err == nil { |
482 | err = starter() |
483 | if err == nil { |
484 | - go func() { sess.ErrCh <- looper() }() |
485 | + go func() { sess.errCh <- looper() }() |
486 | } |
487 | } |
488 | return err |
489 | @@ -684,6 +712,77 @@ |
490 | return sess.run(sess.doClose, sess.addAuthorization, sess.getHosts, sess.connect, sess.start, sess.loop) |
491 | } |
492 | |
493 | +func (sess *clientSession) doKeepConnection() { |
494 | +Loop: |
495 | + for { |
496 | + select { |
497 | + case hasConn := <-sess.connCh: |
498 | + sess.connHandler(hasConn) |
499 | + case <-sess.stopCh: |
500 | + sess.Log.Infof("session shutting down.") |
501 | + sess.connLock.Lock() |
502 | + defer sess.connLock.Unlock() |
503 | + sess.stopRedial() |
504 | + if sess.Connection != nil { |
505 | + sess.Connection.Close() |
506 | + sess.Connection = nil |
507 | + } |
508 | + break Loop |
509 | + case n := <-sess.doneCh: |
510 | + sess.Log.Debugf("connected after %d attempts.", n) |
511 | + case err := <-sess.errCh: |
512 | + sess.errHandler(err) |
513 | + } |
514 | + } |
515 | +} |
516 | + |
517 | +func (sess *clientSession) handleConn(hasConn bool) { |
518 | + sess.lastConn = hasConn |
519 | + |
520 | + // Note this does not depend on the current state! That's because Dial |
521 | + // starts with doClose, which gets you to Disconnected even if you're |
522 | + // connected, and you can call Close when Disconnected without it |
523 | + // losing its stuff. |
524 | + if hasConn { |
525 | + sess.autoRedial() |
526 | + } else { |
527 | + sess.Close() |
528 | + } |
529 | +} |
530 | + |
531 | +func (sess *clientSession) handleErr(err error) { |
532 | + sess.Log.Errorf("session error'ed out with %v", err) |
533 | + sess.stateLock.Lock() |
534 | + if sess.state == Disconnected && sess.lastConn { |
535 | + sess.autoRedial() |
536 | + } |
537 | + sess.stateLock.Unlock() |
538 | +} |
539 | + |
540 | +func (sess *clientSession) KeepConnection() error { |
541 | + sess.stateLock.Lock() |
542 | + defer sess.stateLock.Unlock() |
543 | + if sess.state != Pristine { |
544 | + return errors.New("don't call KeepConnection() on a non-pristine session.") |
545 | + } |
546 | + sess.state = Disconnected |
547 | + |
548 | + go sess.doKeepConnection() |
549 | + |
550 | + return nil |
551 | +} |
552 | + |
553 | +func (sess *clientSession) StopKeepConnection() { |
554 | + sess.setState(Shutdown) |
555 | + close(sess.stopCh) |
556 | +} |
557 | + |
558 | +func (sess *clientSession) HasConnectivity(hasConn bool) error { |
559 | + sess.connCh <- hasConn |
560 | + // XXX throw errors if called from weird state? |
561 | + return nil |
562 | +} |
563 | + |
564 | func init() { |
565 | rand.Seed(time.Now().Unix()) // good enough for us (we're not using it for crypto) |
566 | } |
567 | |
568 | === modified file 'client/session/session_test.go' |
569 | --- client/session/session_test.go 2015-02-13 11:20:41 +0000 |
570 | +++ client/session/session_test.go 2015-03-17 16:47:16 +0000 |
571 | @@ -190,6 +190,24 @@ |
572 | cs.lvls = func() (seenstate.SeenState, error) { return seenstate.NewSqliteSeenState(":memory:") } |
573 | } |
574 | |
575 | +func (cs *clientSessionSuite) TestStateString(c *C) { |
576 | + for _, i := range []struct { |
577 | + v ClientSessionState |
578 | + s string |
579 | + }{ |
580 | + {Error, "Error"}, |
581 | + {Pristine, "Pristine"}, |
582 | + {Disconnected, "Disconnected"}, |
583 | + {Connected, "Connected"}, |
584 | + {Started, "Started"}, |
585 | + {Running, "Running"}, |
586 | + {Shutdown, "Shutdown"}, |
587 | + {Unknown, fmt.Sprintf("??? (%d)", Unknown)}, |
588 | + } { |
589 | + c.Check(i.v.String(), Equals, i.s) |
590 | + } |
591 | +} |
592 | + |
593 | /**************************************************************** |
594 | parseServerAddrSpec() tests |
595 | ****************************************************************/ |
596 | @@ -214,7 +232,6 @@ |
597 | |
598 | func dummyConf() ClientSessionConfig { |
599 | return ClientSessionConfig{ |
600 | - ErrCh: make(chan error, 1), |
601 | BroadcastCh: make(chan *BroadcastNotification, 5), |
602 | NotificationsCh: make(chan AddressedNotification, 5), |
603 | } |
604 | @@ -231,7 +248,9 @@ |
605 | c.Check(sess.redialDelays, DeepEquals, util.Timeouts()) |
606 | // but no root CAs set |
607 | c.Check(sess.TLS.RootCAs, IsNil) |
608 | - c.Check(sess.State(), Equals, Disconnected) |
609 | + c.Check(sess.State(), Equals, Pristine) |
610 | + c.Check(sess.stopCh, NotNil) |
611 | + c.Check(sess.connCh, NotNil) |
612 | } |
613 | |
614 | func (cs *clientSessionSuite) TestNewSessionHostEndpointWorks(c *C) { |
615 | @@ -569,9 +588,9 @@ |
616 | c.Check(ar.stopped, Equals, true) |
617 | } |
618 | |
619 | -/**************************************************************** |
620 | - AutoRedial() tests |
621 | -****************************************************************/ |
622 | +// /**************************************************************** |
623 | +// AutoRedial() tests |
624 | +// ****************************************************************/ |
625 | |
626 | func (cs *clientSessionSuite) TestAutoRedialWorks(c *C) { |
627 | // checks that AutoRedial sets up a retrier and tries redialing it |
628 | @@ -580,7 +599,8 @@ |
629 | ar := new(derp) |
630 | sess.retrier = ar |
631 | c.Check(ar.stopped, Equals, false) |
632 | - sess.AutoRedial(nil) |
633 | + sess.autoRedial() |
634 | + defer sess.stopRedial() |
635 | c.Check(ar.stopped, Equals, true) |
636 | } |
637 | |
638 | @@ -588,20 +608,21 @@ |
639 | // checks that AutoRedial stops the previous retrier |
640 | sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log) |
641 | c.Assert(err, IsNil) |
642 | - ch := make(chan uint32) |
643 | + sess.doneCh = make(chan uint32) |
644 | c.Check(sess.retrier, IsNil) |
645 | - sess.AutoRedial(ch) |
646 | + sess.autoRedial() |
647 | c.Assert(sess.retrier, NotNil) |
648 | sess.retrier.Stop() |
649 | - c.Check(<-ch, Not(Equals), 0) |
650 | + c.Check(<-sess.doneCh, Not(Equals), 0) |
651 | } |
652 | |
653 | func (cs *clientSessionSuite) TestAutoRedialCallsRedialDelay(c *C) { |
654 | + // NOTE there are tests that use calling redialDelay as an indication of calling autoRedial! |
655 | sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log) |
656 | c.Assert(err, IsNil) |
657 | flag := false |
658 | sess.redialDelay = func(sess *clientSession) time.Duration { flag = true; return 0 } |
659 | - sess.AutoRedial(nil) |
660 | + sess.autoRedial() |
661 | c.Check(flag, Equals, true) |
662 | } |
663 | |
664 | @@ -609,11 +630,11 @@ |
665 | sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log) |
666 | c.Assert(err, IsNil) |
667 | sess.redialDelay = func(sess *clientSession) time.Duration { return 0 } |
668 | - sess.AutoRedial(nil) |
669 | + sess.autoRedial() |
670 | c.Check(sess.ShouldDelay(), Equals, false) |
671 | sess.stopRedial() |
672 | sess.clearShouldDelay() |
673 | - sess.AutoRedial(nil) |
674 | + sess.autoRedial() |
675 | c.Check(sess.ShouldDelay(), Equals, true) |
676 | } |
677 | |
678 | @@ -625,7 +646,6 @@ |
679 | sess *clientSession |
680 | upCh chan interface{} |
681 | downCh chan interface{} |
682 | - errCh chan error |
683 | } |
684 | |
685 | var _ = Suite(&msgSuite{}) |
686 | @@ -637,7 +657,6 @@ |
687 | s.sess, err = NewSession("", conf, "wah", seenstate.NewSeenState, helpers.NewTestLogger(c, "debug")) |
688 | c.Assert(err, IsNil) |
689 | s.sess.Connection = &testConn{Name: "TestHandle*"} |
690 | - s.errCh = conf.ErrCh |
691 | s.upCh = make(chan interface{}, 5) |
692 | s.downCh = make(chan interface{}, 5) |
693 | s.sess.proto = &testProtocol{up: s.upCh, down: s.downCh} |
694 | @@ -696,10 +715,10 @@ |
695 | json.RawMessage(`{"img1/m1":[102,"tubular"]}`), |
696 | }, |
697 | } |
698 | - go func() { s.errCh <- s.sess.handleBroadcast(msg) }() |
699 | + go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }() |
700 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
701 | s.upCh <- nil // ack ok |
702 | - c.Check(<-s.errCh, Equals, nil) |
703 | + c.Check(<-s.sess.errCh, Equals, nil) |
704 | c.Assert(len(s.sess.BroadcastCh), Equals, 1) |
705 | c.Check(<-s.sess.BroadcastCh, DeepEquals, &BroadcastNotification{ |
706 | TopLevel: 2, |
707 | @@ -728,11 +747,11 @@ |
708 | TopLevel: 2, |
709 | Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)}, |
710 | } |
711 | - go func() { s.errCh <- s.sess.handleBroadcast(msg) }() |
712 | + go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }() |
713 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
714 | failure := errors.New("ACK ACK ACK") |
715 | s.upCh <- failure |
716 | - c.Assert(<-s.errCh, Equals, failure) |
717 | + c.Assert(<-s.sess.errCh, Equals, failure) |
718 | c.Check(s.sess.State(), Equals, Error) |
719 | } |
720 | |
721 | @@ -746,10 +765,10 @@ |
722 | TopLevel: 2, |
723 | Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)}, |
724 | } |
725 | - go func() { s.errCh <- s.sess.handleBroadcast(msg) }() |
726 | + go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }() |
727 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
728 | s.upCh <- nil // ack ok |
729 | - c.Check(<-s.errCh, IsNil) |
730 | + c.Check(<-s.sess.errCh, IsNil) |
731 | c.Check(len(s.sess.BroadcastCh), Equals, 0) |
732 | } |
733 | |
734 | @@ -764,10 +783,10 @@ |
735 | TopLevel: 2, |
736 | Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)}, |
737 | } |
738 | - go func() { s.errCh <- s.sess.handleBroadcast(msg) }() |
739 | + go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }() |
740 | s.upCh <- nil // ack ok |
741 | // start returns with error |
742 | - c.Check(<-s.errCh, Not(Equals), nil) |
743 | + c.Check(<-s.sess.errCh, Not(Equals), nil) |
744 | c.Check(s.sess.State(), Equals, Error) |
745 | // no message sent out |
746 | c.Check(len(s.sess.BroadcastCh), Equals, 0) |
747 | @@ -780,10 +799,10 @@ |
748 | s.sess.setShouldDelay() |
749 | |
750 | msg := &serverMsg{Type: "broadcast"} |
751 | - go func() { s.errCh <- s.sess.handleBroadcast(msg) }() |
752 | + go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }() |
753 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
754 | s.upCh <- nil // ack ok |
755 | - c.Check(<-s.errCh, IsNil) |
756 | + c.Check(<-s.sess.errCh, IsNil) |
757 | |
758 | c.Check(s.sess.ShouldDelay(), Equals, false) |
759 | } |
760 | @@ -792,10 +811,10 @@ |
761 | s.sess.setShouldDelay() |
762 | |
763 | msg := &serverMsg{Type: "broadcast"} |
764 | - go func() { s.errCh <- s.sess.handleBroadcast(msg) }() |
765 | + go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }() |
766 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
767 | s.upCh <- errors.New("bcast") |
768 | - c.Check(<-s.errCh, NotNil) |
769 | + c.Check(<-s.sess.errCh, NotNil) |
770 | |
771 | c.Check(s.sess.ShouldDelay(), Equals, true) |
772 | } |
773 | @@ -845,10 +864,10 @@ |
774 | msg.NotificationsMsg = protocol.NotificationsMsg{ |
775 | Notifications: []protocol.Notification{n1, n2}, |
776 | } |
777 | - go func() { s.errCh <- s.sess.handleNotifications(msg) }() |
778 | + go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }() |
779 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
780 | s.upCh <- nil // ack ok |
781 | - c.Check(<-s.errCh, Equals, nil) |
782 | + c.Check(<-s.sess.errCh, Equals, nil) |
783 | c.Check(s.sess.ShouldDelay(), Equals, false) |
784 | c.Assert(s.sess.NotificationsCh, HasLen, 2) |
785 | app1, err := click.ParseAppId("com.example.app1_app1") |
786 | @@ -891,10 +910,10 @@ |
787 | msg.NotificationsMsg = protocol.NotificationsMsg{ |
788 | Notifications: []protocol.Notification{n1, n2}, |
789 | } |
790 | - go func() { s.errCh <- s.sess.handleNotifications(msg) }() |
791 | + go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }() |
792 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
793 | s.upCh <- nil // ack ok |
794 | - c.Check(<-s.errCh, Equals, nil) |
795 | + c.Check(<-s.sess.errCh, Equals, nil) |
796 | c.Check(s.sess.ShouldDelay(), Equals, false) |
797 | c.Assert(s.sess.NotificationsCh, HasLen, 1) |
798 | app2, err := click.ParseAppId("com.example.app2_app2") |
799 | @@ -926,10 +945,10 @@ |
800 | msg.NotificationsMsg = protocol.NotificationsMsg{ |
801 | Notifications: []protocol.Notification{n1, n2}, |
802 | } |
803 | - go func() { s.errCh <- s.sess.handleNotifications(msg) }() |
804 | + go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }() |
805 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
806 | s.upCh <- nil // ack ok |
807 | - c.Check(<-s.errCh, Equals, nil) |
808 | + c.Check(<-s.sess.errCh, Equals, nil) |
809 | c.Assert(s.sess.NotificationsCh, HasLen, 2) |
810 | app1, err := click.ParseAppId("com.example.app1_app1") |
811 | c.Assert(err, IsNil) |
812 | @@ -946,10 +965,10 @@ |
813 | c.Check(ac.ops, HasLen, 3) |
814 | |
815 | // second time they get ignored |
816 | - go func() { s.errCh <- s.sess.handleNotifications(msg) }() |
817 | + go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }() |
818 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
819 | s.upCh <- nil // ack ok |
820 | - c.Check(<-s.errCh, Equals, nil) |
821 | + c.Check(<-s.sess.errCh, Equals, nil) |
822 | c.Assert(s.sess.NotificationsCh, HasLen, 0) |
823 | c.Check(ac.ops, HasLen, 4) |
824 | } |
825 | @@ -966,11 +985,11 @@ |
826 | msg.NotificationsMsg = protocol.NotificationsMsg{ |
827 | Notifications: []protocol.Notification{n1}, |
828 | } |
829 | - go func() { s.errCh <- s.sess.handleNotifications(msg) }() |
830 | + go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }() |
831 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
832 | failure := errors.New("ACK ACK ACK") |
833 | s.upCh <- failure |
834 | - c.Assert(<-s.errCh, Equals, failure) |
835 | + c.Assert(<-s.sess.errCh, Equals, failure) |
836 | c.Check(s.sess.State(), Equals, Error) |
837 | // didn't get to clear |
838 | c.Check(s.sess.ShouldDelay(), Equals, true) |
839 | @@ -989,10 +1008,10 @@ |
840 | msg.NotificationsMsg = protocol.NotificationsMsg{ |
841 | Notifications: []protocol.Notification{n1}, |
842 | } |
843 | - go func() { s.errCh <- s.sess.handleNotifications(msg) }() |
844 | + go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }() |
845 | s.upCh <- nil // ack ok |
846 | // start returns with error |
847 | - c.Check(<-s.errCh, Not(Equals), nil) |
848 | + c.Check(<-s.sess.errCh, Not(Equals), nil) |
849 | c.Check(s.sess.State(), Equals, Error) |
850 | // no message sent out |
851 | c.Check(len(s.sess.NotificationsCh), Equals, 0) |
852 | @@ -1013,8 +1032,8 @@ |
853 | msg.ConnBrokenMsg = protocol.ConnBrokenMsg{ |
854 | Reason: "REASON", |
855 | } |
856 | - go func() { s.errCh <- s.sess.handleConnBroken(msg) }() |
857 | - c.Check(<-s.errCh, ErrorMatches, "server broke connection: REASON") |
858 | + go func() { s.sess.errCh <- s.sess.handleConnBroken(msg) }() |
859 | + c.Check(<-s.sess.errCh, ErrorMatches, "server broke connection: REASON") |
860 | c.Check(s.sess.State(), Equals, Error) |
861 | } |
862 | |
863 | @@ -1025,8 +1044,8 @@ |
864 | Reason: protocol.BrokenHostMismatch, |
865 | } |
866 | s.sess.deliveryHosts = []string{"foo:443", "bar:443"} |
867 | - go func() { s.errCh <- s.sess.handleConnBroken(msg) }() |
868 | - c.Check(<-s.errCh, ErrorMatches, "server broke connection: host-mismatch") |
869 | + go func() { s.sess.errCh <- s.sess.handleConnBroken(msg) }() |
870 | + c.Check(<-s.sess.errCh, ErrorMatches, "server broke connection: host-mismatch") |
871 | c.Check(s.sess.State(), Equals, Error) |
872 | // hosts were reset |
873 | c.Check(s.sess.deliveryHosts, IsNil) |
874 | @@ -1044,14 +1063,14 @@ |
875 | (*msgSuite)(s).SetUpTest(c) |
876 | s.sess.Connection.(*testConn).Name = "TestLoop*" |
877 | go func() { |
878 | - s.errCh <- s.sess.loop() |
879 | + s.sess.errCh <- s.sess.loop() |
880 | }() |
881 | } |
882 | |
883 | func (s *loopSuite) TestLoopReadError(c *C) { |
884 | c.Check(s.sess.State(), Equals, Running) |
885 | s.upCh <- errors.New("Read") |
886 | - err := <-s.errCh |
887 | + err := <-s.sess.errCh |
888 | c.Check(err, ErrorMatches, "Read") |
889 | c.Check(s.sess.State(), Equals, Error) |
890 | } |
891 | @@ -1063,7 +1082,7 @@ |
892 | c.Check(takeNext(s.downCh), Equals, protocol.PingPongMsg{Type: "pong"}) |
893 | failure := errors.New("pong") |
894 | s.upCh <- failure |
895 | - c.Check(<-s.errCh, Equals, failure) |
896 | + c.Check(<-s.sess.errCh, Equals, failure) |
897 | } |
898 | |
899 | func (s *loopSuite) TestLoopLoopsDaLoop(c *C) { |
900 | @@ -1076,7 +1095,7 @@ |
901 | } |
902 | failure := errors.New("pong") |
903 | s.upCh <- failure |
904 | - c.Check(<-s.errCh, Equals, failure) |
905 | + c.Check(<-s.sess.errCh, Equals, failure) |
906 | } |
907 | |
908 | func (s *loopSuite) TestLoopBroadcast(c *C) { |
909 | @@ -1093,7 +1112,7 @@ |
910 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
911 | failure := errors.New("ack") |
912 | s.upCh <- failure |
913 | - c.Check(<-s.errCh, Equals, failure) |
914 | + c.Check(<-s.sess.errCh, Equals, failure) |
915 | } |
916 | |
917 | func (s *loopSuite) TestLoopNotifications(c *C) { |
918 | @@ -1113,7 +1132,7 @@ |
919 | c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) |
920 | failure := errors.New("ack") |
921 | s.upCh <- failure |
922 | - c.Check(<-s.errCh, Equals, failure) |
923 | + c.Check(<-s.sess.errCh, Equals, failure) |
924 | } |
925 | |
926 | func (s *loopSuite) TestLoopSetParams(c *C) { |
927 | @@ -1126,7 +1145,7 @@ |
928 | s.upCh <- setParams |
929 | failure := errors.New("fail") |
930 | s.upCh <- failure |
931 | - c.Assert(<-s.errCh, Equals, failure) |
932 | + c.Assert(<-s.sess.errCh, Equals, failure) |
933 | c.Check(s.sess.getCookie(), Equals, "COOKIE") |
934 | } |
935 | |
936 | @@ -1138,7 +1157,7 @@ |
937 | } |
938 | c.Check(takeNext(s.downCh), Equals, "deadline 1ms") |
939 | s.upCh <- broken |
940 | - c.Check(<-s.errCh, NotNil) |
941 | + c.Check(<-s.sess.errCh, NotNil) |
942 | } |
943 | |
944 | func (s *loopSuite) TestLoopConnWarn(c *C) { |
945 | @@ -1159,7 +1178,7 @@ |
946 | s.upCh <- warn |
947 | s.upCh <- connwarn |
948 | s.upCh <- failure |
949 | - c.Check(<-s.errCh, Equals, failure) |
950 | + c.Check(<-s.sess.errCh, Equals, failure) |
951 | c.Check(log.Captured(), |
952 | Matches, `(?ms).* warning: XXX$.*`) |
953 | c.Check(log.Captured(), |
954 | @@ -1426,12 +1445,12 @@ |
955 | func() error { sess.BroadcastCh <- notf; return <-failureCh }) |
956 | c.Check(err, Equals, nil) |
957 | // if run doesn't error it sets up the channels |
958 | - c.Assert(sess.ErrCh, NotNil) |
959 | + c.Assert(sess.errCh, NotNil) |
960 | c.Assert(sess.BroadcastCh, NotNil) |
961 | c.Check(<-sess.BroadcastCh, Equals, notf) |
962 | failure := errors.New("TestRunRunsEvenIfLoopFails") |
963 | failureCh <- failure |
964 | - c.Check(<-sess.ErrCh, Equals, failure) |
965 | + c.Check(<-sess.errCh, Equals, failure) |
966 | // so now you know it was running in a goroutine :) |
967 | } |
968 | |
969 | @@ -1632,7 +1651,7 @@ |
970 | c.Check(takeNext(downCh), Equals, protocol.PingPongMsg{Type: "pong"}) |
971 | failure := errors.New("pongs") |
972 | upCh <- failure |
973 | - c.Check(<-sess.ErrCh, Equals, failure) |
974 | + c.Check(<-sess.errCh, Equals, failure) |
975 | } |
976 | |
977 | func (cs *clientSessionSuite) TestDialWorksDirect(c *C) { |
978 | @@ -1703,3 +1722,191 @@ |
979 | sess.ClearCookie() |
980 | c.Check(sess.getCookie(), Equals, "") |
981 | } |
982 | + |
983 | +/**************************************************************** |
984 | + KeepConnection() (and related) tests |
985 | +****************************************************************/ |
986 | + |
987 | +func (cs *clientSessionSuite) TestKeepConnectionDoesNothingIfNotConnected(c *C) { |
988 | + // how do you test "does nothing?" |
989 | + sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log) |
990 | + c.Assert(err, IsNil) |
991 | + c.Assert(sess, NotNil) |
992 | + c.Assert(sess.State(), Equals, Pristine) |
993 | + c.Assert(sess.KeepConnection(), IsNil) |
994 | + // stopCh is meant to be used just for closing it, but abusing |
995 | + // it for testing seems the right thing to do: this ensures |
996 | + // the thing is ticking along before we check the state of |
997 | + // stuff. |
998 | + sess.stopCh <- struct{}{} |
999 | + c.Check(sess.State(), Equals, Disconnected) |
1000 | +} |
1001 | + |
1002 | +func (cs *clientSessionSuite) TestYouCantCallKeepConnectionTwice(c *C) { |
1003 | + sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log) |
1004 | + c.Assert(err, IsNil) |
1005 | + c.Assert(sess, NotNil) |
1006 | + c.Assert(sess.State(), Equals, Pristine) |
1007 | + c.Assert(sess.KeepConnection(), IsNil) |
1008 | + defer sess.StopKeepConnection() |
1009 | + c.Check(sess.KeepConnection(), NotNil) |
1010 | +} |
1011 | + |
1012 | +func (cs *clientSessionSuite) TestStopKeepConnectionShutsdown(c *C) { |
1013 | + sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log) |
1014 | + c.Assert(err, IsNil) |
1015 | + c.Assert(sess, NotNil) |
1016 | + sess.StopKeepConnection() |
1017 | + c.Check(sess.State(), Equals, Shutdown) |
1018 | +} |
1019 | + |
1020 | +func (cs *clientSessionSuite) TestHasConnectivityTriggersConnectivityHandler(c *C) { |
1021 | + sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log) |
1022 | + c.Assert(err, IsNil) |
1023 | + c.Assert(sess, NotNil) |
1024 | + testCh := make(chan bool) |
1025 | + sess.connHandler = func(p bool) { testCh <- p } |
1026 | + go sess.doKeepConnection() |
1027 | + defer sess.StopKeepConnection() |
1028 | + sess.HasConnectivity(true) |
1029 | + c.Check(<-testCh, Equals, true) |
1030 | + sess.HasConnectivity(false) |
1031 | + c.Check(<-testCh, Equals, false) |
1032 | +} |
1033 | + |
1034 | +func (cs *clientSessionSuite) TestDoneChIsEmptiedAndLogged(c *C) { |
1035 | + sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log) |
1036 | + c.Assert(err, IsNil) |
1037 | + sess.doneCh = make(chan uint32) // unbuffered |
1038 | + |
1039 | + sess.KeepConnection() |
1040 | + defer sess.StopKeepConnection() |
1041 | + |
1042 | + sess.doneCh <- 23 |
1043 | + |
1044 | + c.Check(cs.log.Captured(), |
1045 | + Matches, `(?ms).* connected after 23 attempts\.`) |
1046 | +} |
1047 | + |
1048 | +func (cs *clientSessionSuite) TestErrChIsEmptiedAndLoggedAndAutoRedial(c *C) { |
1049 | + sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log) |
1050 | + c.Assert(err, IsNil) |
1051 | + ch := make(chan struct{}, 1) |
1052 | + sess.errCh = make(chan error) // unbuffered |
1053 | + sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 } |
1054 | + sess.lastConn = true // -> autoRedial, if the session is in Disconnected |
1055 | + |
1056 | + sess.KeepConnection() |
1057 | + defer sess.StopKeepConnection() |
1058 | + |
1059 | + sess.errCh <- errors.New("potato") |
1060 | + c.Assert(sess.State(), Equals, Disconnected) |
1061 | + select { |
1062 | + case <-ch: |
1063 | + // all ok |
1064 | + case <-time.After(100 * time.Millisecond): |
1065 | + c.Fatalf("redialDelay not called (-> autoRedial not called)?") |
1066 | + } |
1067 | + |
1068 | + c.Check(cs.log.Captured(), |
1069 | + Matches, `(?ms).* session error.*potato`) |
1070 | +} |
1071 | + |
1072 | +func (cs *clientSessionSuite) TestErrChIsEmptiedAndLoggedNoAutoRedial(c *C) { |
1073 | + sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log) |
1074 | + c.Assert(err, IsNil) |
1075 | + ch := make(chan struct{}, 1) |
1076 | + sess.errCh = make(chan error) // unbuffered |
1077 | + sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 } |
1078 | + sess.connHandler = func(bool) {} |
1079 | + sess.lastConn = false // so, no autoredial |
1080 | + |
1081 | + sess.KeepConnection() |
1082 | + defer sess.StopKeepConnection() |
1083 | + |
1084 | + sess.errCh <- errors.New("potato") |
1085 | + c.Assert(sess.State(), Equals, Disconnected) |
1086 | + select { |
1087 | + case <-ch: |
1088 | + c.Fatalf("redialDelay called (-> autoRedial called) when disconnected?") |
1089 | + case <-time.After(100 * time.Millisecond): |
1090 | + // all ok |
1091 | + } |
1092 | + |
1093 | + c.Check(cs.log.Captured(), |
1094 | + Matches, `(?ms).* session error.*potato`) |
1095 | +} |
1096 | + |
1097 | +func (cs *clientSessionSuite) TestHandleConnConnFromConnected(c *C) { |
1098 | + sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log) |
1099 | + c.Assert(err, IsNil) |
1100 | + ch := make(chan struct{}, 1) |
1101 | + sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 } |
1102 | + sess.state = Connected |
1103 | + sess.lastConn = true |
1104 | + sess.handleConn(true) |
1105 | + c.Check(sess.lastConn, Equals, true) |
1106 | + |
1107 | + select { |
1108 | + case <-ch: |
1109 | + // all ok |
1110 | + case <-time.After(100 * time.Millisecond): |
1111 | + c.Fatalf("redialDelay not called (-> autoRedial not called)?") |
1112 | + } |
1113 | +} |
1114 | + |
1115 | +func (cs *clientSessionSuite) TestHandleConnConnFromDisconnected(c *C) { |
1116 | + sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log) |
1117 | + c.Assert(err, IsNil) |
1118 | + ch := make(chan struct{}, 1) |
1119 | + sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 } |
1120 | + sess.state = Disconnected |
1121 | + sess.lastConn = false |
1122 | + sess.handleConn(true) |
1123 | + c.Check(sess.lastConn, Equals, true) |
1124 | + |
1125 | + select { |
1126 | + case <-ch: |
1127 | + // all ok |
1128 | + case <-time.After(100 * time.Millisecond): |
1129 | + c.Fatalf("redialDelay not called (-> autoRedial not called)?") |
1130 | + } |
1131 | +} |
1132 | + |
1133 | +func (cs *clientSessionSuite) TestHandleConnNotConnFromDisconnected(c *C) { |
1134 | + sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log) |
1135 | + c.Assert(err, IsNil) |
1136 | + ch := make(chan struct{}, 1) |
1137 | + sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 } |
1138 | + sess.state = Disconnected |
1139 | + sess.lastConn = false |
1140 | + sess.handleConn(false) |
1141 | + c.Check(sess.lastConn, Equals, false) |
1142 | + |
1143 | + select { |
1144 | + case <-ch: |
1145 | + c.Fatalf("redialDelay called (-> autoRedial called)?") |
1146 | + case <-time.After(100 * time.Millisecond): |
1147 | + // all ok |
1148 | + } |
1149 | + c.Check(cs.log.Captured(), Matches, `(?ms).*-> Disconnected`) |
1150 | +} |
1151 | + |
1152 | +func (cs *clientSessionSuite) TestHandleConnNotConnFromConnected(c *C) { |
1153 | + sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log) |
1154 | + c.Assert(err, IsNil) |
1155 | + ch := make(chan struct{}, 1) |
1156 | + sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 } |
1157 | + sess.state = Connected |
1158 | + sess.lastConn = true |
1159 | + sess.handleConn(false) |
1160 | + c.Check(sess.lastConn, Equals, false) |
1161 | + |
1162 | + select { |
1163 | + case <-ch: |
1164 | + c.Fatalf("redialDelay called (-> autoRedial called)?") |
1165 | + case <-time.After(100 * time.Millisecond): |
1166 | + // all ok |
1167 | + } |
1168 | + c.Check(cs.log.Captured(), Matches, `(?ms).*-> Disconnected`) |
1169 | +} |
see comments