Merge lp:~chipaca/ubuntu-push/client-v0-p11 into lp:ubuntu-push

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: 60
Merged at revision: 51
Proposed branch: lp:~chipaca/ubuntu-push/client-v0-p11
Merge into: lp:ubuntu-push
Prerequisite: lp:~chipaca/ubuntu-push/client-v0-p9
Diff against target: 387 lines (+118/-18)
3 files modified
client/client.go (+31/-6)
client/client_test.go (+79/-12)
client/session/session.go (+8/-0)
To merge this branch: bzr merge lp:~chipaca/ubuntu-push/client-v0-p11
Reviewer Review Type Date Requested Status
Samuele Pedroni Approve
Review via email: mp+204696@code.launchpad.net

Commit message

Several things:
* features:
  * Client.doLoop, the puppet master.
* fixes and cleanups:
  * added log_level to client config
  * added the mysterious sessionRetryCh, used in doLoop to avoid a rather common starvation scenario.
  * found a way not to panic out in initSession (not that it's much better)
  * unified logging in the client tests a bit
  * added logging to session's start error states.

Description of the change

Several things:
* features:
  * Client.doLoop, the puppet master.
* fixes and cleanups:
  * added log_level to client config
  * added the mysterious sessionRetryCh, used in doLoop to avoid a rather common starvation scenario.
  * found a way not to panic out in initSession (not that it's much better)
  * unified logging in the client tests a bit
  * added logging to session's start error states.

To post a comment you must log in.
Revision history for this message
Samuele Pedroni (pedronis) wrote :

ok but land with todo markers

=== modified file 'client/client.go'
--- client/client.go 2014-02-04 13:10:18 +0000
+++ client/client.go 2014-02-04 17:34:01 +0000
@@ -142,6 +142,7 @@

 // connectSession kicks off the session connection dance
 func (client *Client) connectSession() {
+ // xxx lp:1276199
  if client.sessionRetrierStopper != nil {
   client.sessionRetrierStopper <- true
   client.sessionRetrierStopper = nil
@@ -156,6 +157,7 @@

 // disconnectSession disconnects the session
 func (client *Client) disconnectSession() {
+ // xxx lp:1276199
  if client.sessionRetrierStopper != nil {
   client.sessionRetrierStopper <- true

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

// ClientConfig holds the client configuration
type ClientConfig struct {
        connectivity.ConnectivityConfig // q.v.
        // A reasonably large maximum ping time

that's probably more: A reasonably large timeout for receive/answer pairs

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

ok, but see bug/markers above and as discussed

review: Approve
60. By John Lenton

added XXX comments, and fixed ExchangeTimeout docstring, as per pedronis suggestions

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

Done. Also added auto-highlighting of XXX comments to my .emacs... :-)

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 2014-02-04 18:19:28 +0000
3+++ client/client.go 2014-02-04 18:19:28 +0000
4@@ -39,12 +39,14 @@
5 // ClientConfig holds the client configuration
6 type ClientConfig struct {
7 connectivity.ConnectivityConfig // q.v.
8- // A reasonably large maximum ping time
9+ // A reasonably larg timeout for receive/answer pairs
10 ExchangeTimeout config.ConfigTimeDuration `json:"exchange_timeout"`
11 // The server to connect to
12 Addr config.ConfigHostPort
13 // The PEM-encoded server certificate
14 CertPEMFile string `json:"cert_pem_file"`
15+ // The logging level (one of "debug", "info", "error")
16+ LogLevel string `json:"log_level"`
17 }
18
19 // Client is the Ubuntu Push Notifications client-side daemon.
20@@ -62,6 +64,7 @@
21 actionsCh <-chan notifications.RawActionReply
22 session *session.ClientSession
23 sessionRetrierStopper chan bool
24+ sessionRetryCh chan uint32
25 }
26
27 // Configure loads the configuration specified in configPath, and sets it up.
28@@ -74,8 +77,8 @@
29 if err != nil {
30 return fmt.Errorf("reading config: %v", err)
31 }
32- // later, we'll be specifying logging options in the config file
33- client.log = logger.NewSimpleLogger(os.Stderr, "error")
34+ // later, we'll be specifying more logging options in the config file
35+ client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel)
36
37 // overridden for testing
38 client.idder = identifier.New()
39@@ -84,6 +87,7 @@
40 client.connectivityEndp = bus.SystemBus.Endpoint(networkmanager.BusAddress, client.log)
41
42 client.connCh = make(chan bool)
43+ client.sessionRetryCh = make(chan uint32)
44
45 if client.config.CertPEMFile != "" {
46 client.pem, err = ioutil.ReadFile(client.config.CertPEMFile)
47@@ -126,17 +130,19 @@
48 }
49
50 // initSession creates the session object
51-func (client *Client) initSession() {
52+func (client *Client) initSession() error {
53 sess, err := session.NewSession(string(client.config.Addr), client.pem,
54 client.config.ExchangeTimeout.Duration, client.deviceId, client.log)
55 if err != nil {
56- panic("Don't know how to handle session creation failure.")
57+ return err
58 }
59 client.session = sess
60+ return nil
61 }
62
63 // connectSession kicks off the session connection dance
64 func (client *Client) connectSession() {
65+ // XXX: lp:1276199
66 if client.sessionRetrierStopper != nil {
67 client.sessionRetrierStopper <- true
68 client.sessionRetrierStopper = nil
69@@ -146,11 +152,12 @@
70 client.session.Dial,
71 util.Jitter}
72 client.sessionRetrierStopper = ar.Stop
73- go ar.Retry()
74+ go func() { client.sessionRetryCh <- ar.Retry() }()
75 }
76
77 // disconnectSession disconnects the session
78 func (client *Client) disconnectSession() {
79+ // XXX: lp:1276199
80 if client.sessionRetrierStopper != nil {
81 client.sessionRetrierStopper <- true
82 client.sessionRetrierStopper = nil
83@@ -212,3 +219,21 @@
84 urld := urldispatcher.New(client.urlDispatcherEndp, client.log)
85 return urld.DispatchURL("settings:///system/system-update")
86 }
87+
88+// doLoop connects events with their handlers
89+func (client *Client) doLoop(connhandler func(bool), clickhandler, notifhandler func() error, errhandler func(error)) {
90+ for {
91+ select {
92+ case state := <-client.connCh:
93+ connhandler(state)
94+ case <-client.actionsCh:
95+ clickhandler()
96+ case <-client.session.MsgCh:
97+ notifhandler()
98+ case err := <-client.session.ErrCh:
99+ errhandler(err)
100+ case count := <-client.sessionRetryCh:
101+ client.log.Debugf("Session connected after %d attempts", count)
102+ }
103+ }
104+}
105
106=== modified file 'client/client_test.go'
107--- client/client_test.go 2014-02-04 18:19:28 +0000
108+++ client/client_test.go 2014-02-04 18:19:28 +0000
109@@ -18,10 +18,12 @@
110
111 import (
112 "bytes"
113+ "errors"
114 "fmt"
115 "io/ioutil"
116 . "launchpad.net/gocheck"
117 "launchpad.net/ubuntu-push/bus/networkmanager"
118+ "launchpad.net/ubuntu-push/bus/notifications"
119 testibus "launchpad.net/ubuntu-push/bus/testing"
120 "launchpad.net/ubuntu-push/client/session"
121 "launchpad.net/ubuntu-push/logger"
122@@ -57,6 +59,7 @@
123
124 var nullog = logger.NewSimpleLogger(ioutil.Discard, "error")
125 var noisylog = logger.NewSimpleLogger(os.Stderr, "debug")
126+var debuglog = noisylog
127 var _ = Suite(&clientSuite{})
128
129 const (
130@@ -82,6 +85,7 @@
131 }
132
133 func (cs *clientSuite) SetUpTest(c *C) {
134+ debuglog.Debugf("---")
135 dir := c.MkDir()
136 cs.configPath = filepath.Join(dir, "config")
137 cfg := fmt.Sprintf(`
138@@ -92,7 +96,8 @@
139 "connectivity_check_md5": "",
140 "addr": ":0",
141 "cert_pem_file": %#v,
142- "recheck_timeout": "3h"
143+ "recheck_timeout": "3h",
144+ "log_level": "debug"
145 }`, helpers.SourceRelative("../server/acceptance/config/testing.cert"))
146 ioutil.WriteFile(cs.configPath, []byte(cfg), 0600)
147 }
148@@ -205,6 +210,7 @@
149
150 func (cs *clientSuite) TestGetDeviceIdWorks(c *C) {
151 cli := new(Client)
152+ cli.log = debuglog
153 cli.idder = identifier.New()
154 c.Check(cli.deviceId, Equals, "")
155 c.Check(cli.getDeviceId(), IsNil)
156@@ -213,6 +219,7 @@
157
158 func (cs *clientSuite) TestGetDeviceIdCanFail(c *C) {
159 cli := new(Client)
160+ cli.log = debuglog
161 cli.idder = idtesting.Failing()
162 c.Check(cli.deviceId, Equals, "")
163 c.Check(cli.getDeviceId(), NotNil)
164@@ -237,8 +244,10 @@
165 cEndp := testibus.NewTestingEndpoint(cCond, condition.Work(true),
166 uint32(networkmanager.ConnectedGlobal),
167 )
168+ testibus.SetWatchTicker(cEndp, make(chan bool))
169 // ok, create the thing
170 cli := new(Client)
171+ cli.log = debuglog
172 err := cli.Configure(cs.configPath)
173 c.Assert(err, IsNil)
174 // the user actions channel has not been set up
175@@ -267,6 +276,7 @@
176 func (cs *clientSuite) TestTakeTheBusCanFail(c *C) {
177 cli := new(Client)
178 err := cli.Configure(cs.configPath)
179+ cli.log = debuglog
180 c.Assert(err, IsNil)
181 // the user actions channel has not been set up
182 c.Check(cli.actionsCh, IsNil)
183@@ -285,15 +295,13 @@
184 ******************************************************************/
185
186 func (cs *clientSuite) TestHandleErr(c *C) {
187+ buf := &bytes.Buffer{}
188 cli := new(Client)
189- cli.log = noisylog
190+ cli.log = logger.NewSimpleLogger(buf, "debug")
191 cli.initSession()
192 cli.hasConnectivity = true
193- cli.handleErr(nil)
194- c.Assert(cli.session, NotNil)
195- // let the session connection fail
196- time.Sleep(100 * time.Millisecond)
197- c.Check(cli.session.State(), Equals, session.Error)
198+ cli.handleErr(errors.New("bananas"))
199+ c.Check(buf.String(), Matches, ".*session exited.*bananas\n")
200 }
201
202 /*****************************************************************
203@@ -302,19 +310,23 @@
204
205 func (cs *clientSuite) TestHandleConnStateD2C(c *C) {
206 cli := new(Client)
207+ cli.log = debuglog
208 cli.initSession()
209 // let's pretend the client had a previous attempt at connecting still pending
210 // (hard to trigger in real life, but possible)
211- cli.sessionRetrierStopper = make(chan bool, 1)
212+ ch := make(chan bool, 1)
213+ cli.sessionRetrierStopper = ch
214
215 c.Assert(cli.hasConnectivity, Equals, false)
216 cli.handleConnState(true)
217+ c.Check(len(ch), Equals, 1)
218 c.Check(cli.hasConnectivity, Equals, true)
219 c.Assert(cli.session, NotNil)
220 }
221
222 func (cs *clientSuite) TestHandleConnStateSame(c *C) {
223 cli := new(Client)
224+ cli.log = debuglog
225 // here we want to check that we don't do anything
226 c.Assert(cli.session, IsNil)
227 c.Assert(cli.hasConnectivity, Equals, false)
228@@ -328,7 +340,8 @@
229
230 func (cs *clientSuite) TestHandleConnStateC2D(c *C) {
231 cli := new(Client)
232- cli.session, _ = session.NewSession(string(cli.config.Addr), cli.pem, cli.config.ExchangeTimeout.Duration, cli.deviceId, noisylog)
233+ cli.log = debuglog
234+ cli.session, _ = session.NewSession(string(cli.config.Addr), cli.pem, cli.config.ExchangeTimeout.Duration, cli.deviceId, debuglog)
235 cli.session.Dial()
236 cli.hasConnectivity = true
237
238@@ -340,7 +353,8 @@
239
240 func (cs *clientSuite) TestHandleConnStateC2DPending(c *C) {
241 cli := new(Client)
242- cli.session, _ = session.NewSession(string(cli.config.Addr), cli.pem, cli.config.ExchangeTimeout.Duration, cli.deviceId, noisylog)
243+ cli.log = debuglog
244+ cli.session, _ = session.NewSession(string(cli.config.Addr), cli.pem, cli.config.ExchangeTimeout.Duration, cli.deviceId, debuglog)
245 cli.sessionRetrierStopper = make(chan bool, 1)
246 cli.hasConnectivity = true
247
248@@ -369,9 +383,9 @@
249
250 func (cs *clientSuite) TestHandleNotificationFail(c *C) {
251 cli := new(Client)
252+ cli.log = debuglog
253 endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
254 cli.notificationsEndp = endp
255- cli.log = noisylog
256 c.Check(cli.handleNotification(), NotNil)
257 }
258
259@@ -381,9 +395,9 @@
260
261 func (cs *clientSuite) TestHandleClick(c *C) {
262 cli := new(Client)
263+ cli.log = debuglog
264 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), nil)
265 cli.urlDispatcherEndp = endp
266- cli.log = noisylog
267 c.Check(cli.handleClick(), IsNil)
268 // check we sent the notification
269 args := testibus.GetCallArgs(endp)
270@@ -391,3 +405,56 @@
271 c.Check(args[0].Member, Equals, "DispatchURL")
272 c.Check(args[0].Args, DeepEquals, []interface{}{"settings:///system/system-update"})
273 }
274+
275+/*****************************************************************
276+ doLoop tests
277+******************************************************************/
278+
279+func (cs *clientSuite) TestDoLoopConn(c *C) {
280+ cli := new(Client)
281+ cli.log = debuglog
282+ cli.connCh = make(chan bool, 1)
283+ cli.connCh <- true
284+ cli.initSession()
285+
286+ ch := make(chan bool, 1)
287+ go cli.doLoop(func(bool) { ch <- true }, func() error { return nil }, func() error { return nil }, func(error) {})
288+ c.Check(takeNextBool(ch), Equals, true)
289+}
290+
291+func (cs *clientSuite) TestDoLoopClick(c *C) {
292+ cli := new(Client)
293+ cli.log = debuglog
294+ cli.initSession()
295+ aCh := make(chan notifications.RawActionReply, 1)
296+ aCh <- notifications.RawActionReply{}
297+ cli.actionsCh = aCh
298+
299+ ch := make(chan bool, 1)
300+ go cli.doLoop(func(bool) {}, func() error { ch <- true; return nil }, func() error { return nil }, func(error) {})
301+ c.Check(takeNextBool(ch), Equals, true)
302+}
303+
304+func (cs *clientSuite) TestDoLoopNotif(c *C) {
305+ cli := new(Client)
306+ cli.log = debuglog
307+ cli.initSession()
308+ cli.session.MsgCh = make(chan *session.Notification, 1)
309+ cli.session.MsgCh <- &session.Notification{}
310+
311+ ch := make(chan bool, 1)
312+ go cli.doLoop(func(bool) {}, func() error { return nil }, func() error { ch <- true; return nil }, func(error) {})
313+ c.Check(takeNextBool(ch), Equals, true)
314+}
315+
316+func (cs *clientSuite) TestDoLoopErr(c *C) {
317+ cli := new(Client)
318+ cli.log = debuglog
319+ cli.initSession()
320+ cli.session.ErrCh = make(chan error, 1)
321+ cli.session.ErrCh <- nil
322+
323+ ch := make(chan bool, 1)
324+ go cli.doLoop(func(bool) {}, func() error { return nil }, func() error { return nil }, func(error) { ch <- true })
325+ c.Check(takeNextBool(ch), Equals, true)
326+}
327
328=== modified file 'client/session/session.go'
329--- client/session/session.go 2014-02-04 13:02:38 +0000
330+++ client/session/session.go 2014-02-04 18:19:28 +0000
331@@ -147,14 +147,17 @@
332 err := sess.proto.WriteMessage(protocol.AckMsg{"ack"})
333 if err != nil {
334 sess.setState(Error)
335+ sess.Log.Errorf("unable to ack broadcast: %s", err)
336 return err
337 }
338 sess.Log.Debugf("broadcast chan:%v app:%v topLevel:%d payloads:%s",
339 bcast.ChanId, bcast.AppId, bcast.TopLevel, bcast.Payloads)
340 if bcast.ChanId == protocol.SystemChannelId {
341 // the system channel id, the only one we care about for now
342+ sess.Log.Debugf("sending it over")
343 sess.Levels.Set(bcast.ChanId, bcast.TopLevel)
344 sess.MsgCh <- &Notification{}
345+ sess.Log.Debugf("sent it over")
346 } else {
347 sess.Log.Debugf("what is this weird channel, %#v?", bcast.ChanId)
348 }
349@@ -192,6 +195,7 @@
350 err := conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout))
351 if err != nil {
352 sess.setState(Error)
353+ sess.Log.Errorf("unable to start: set deadline: %s", err)
354 return err
355 }
356 _, err = conn.Write(wireVersionBytes)
357@@ -199,6 +203,7 @@
358 // n < len(p). So, no need to check number of bytes written, hooray.
359 if err != nil {
360 sess.setState(Error)
361+ sess.Log.Errorf("unable to start: write version: %s", err)
362 return err
363 }
364 proto := sess.Protocolator(conn)
365@@ -210,12 +215,14 @@
366 })
367 if err != nil {
368 sess.setState(Error)
369+ sess.Log.Errorf("unable to start: connect: %s", err)
370 return err
371 }
372 var connAck protocol.ConnAckMsg
373 err = proto.ReadMessage(&connAck)
374 if err != nil {
375 sess.setState(Error)
376+ sess.Log.Errorf("unable to start: connack: %s", err)
377 return err
378 }
379 if connAck.Type != "connack" {
380@@ -225,6 +232,7 @@
381 pingInterval, err := time.ParseDuration(connAck.Params.PingInterval)
382 if err != nil {
383 sess.setState(Error)
384+ sess.Log.Errorf("unable to start: parse ping interval: %s", err)
385 return err
386 }
387 sess.proto = proto

Subscribers

People subscribed via source and target branches