Merge lp:~chipaca/ubuntu-push/prepersistance into lp:ubuntu-push

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: 63
Merged at revision: 63
Proposed branch: lp:~chipaca/ubuntu-push/prepersistance
Merge into: lp:ubuntu-push
Prerequisite: lp:~chipaca/ubuntu-push/client-constructor
Diff against target: 244 lines (+107/-21)
4 files modified
client/session/levelmap/levelmap.go (+8/-7)
client/session/levelmap/levelmap_test.go (+22/-8)
client/session/session.go (+22/-4)
client/session/session_test.go (+55/-2)
To merge this branch: bzr merge lp:~chipaca/ubuntu-push/prepersistance
Reviewer Review Type Date Requested Status
Samuele Pedroni Approve
Review via email: mp+205473@code.launchpad.net

Commit message

change the levelmap interface to return errors

Description of the change

In preparation for the persistence branch, change the levelmap interface to (also) return errors.

To post a comment you must log in.
61. By John Lenton

Merged client-constructor into prepersistance.

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

109 - err := sess.proto.WriteMessage(protocol.AckMsg{"ack"})
110 + err := sess.Levels.Set(bcast.ChanId, bcast.TopLevel)
111 + if err != nil {
112 + sess.setState(Error)
113 + sess.Log.Errorf("unable to set level: %v", err)
114 + return err
115 + }
116 + // the server assumes if we ack the broadcast, we've updated
117 + // our levels. Hence the order.

the server will wait ExchangeTimeout and then kill the session, quicker to send a nack? it seems a serious problem on the client, not sure how you expect to recover without user intervention?

118 + err = sess.proto.WriteMessage(protocol.AckMsg{"ack"})

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

        state := uint32(Disconnected)
        levels, err := levelmap.NewLevelMap()
        if err != nil {
                return nil, err
        }

the error path there is not covered fwiw

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

71 + c.Check(err, IsNil)

should this one and similar be Assert?

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

Yes! yes, it should send a nack. Fixing.

The error path gets covered further down the pipeline, when there is a levelmap that can actually return non-nil error.

I use c.Assert on the error when I need to dereference the result (and in this case I don't). Otherwise I'd rather let it go ahead.

62. By John Lenton

send nack on levelmap set error

63. By John Lenton

nak, not nack

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

looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'client/session/levelmap/levelmap.go'
2--- client/session/levelmap/levelmap.go 2014-01-30 10:46:21 +0000
3+++ client/session/levelmap/levelmap.go 2014-02-12 13:24:35 +0000
4@@ -24,23 +24,24 @@
5
6 type LevelMap interface {
7 // Set() (re)sets the given level to the given value.
8- Set(level string, top int64)
9+ Set(level string, top int64) error
10 // GetAll() returns a "simple" map of the current levels.
11- GetAll() map[string]int64
12+ GetAll() (map[string]int64, error)
13 }
14
15 type mapLevelMap map[string]int64
16
17-func (m *mapLevelMap) Set(level string, top int64) {
18+func (m *mapLevelMap) Set(level string, top int64) error {
19 (*m)[level] = top
20+ return nil
21 }
22-func (m *mapLevelMap) GetAll() map[string]int64 {
23- return map[string]int64(*m)
24+func (m *mapLevelMap) GetAll() (map[string]int64, error) {
25+ return map[string]int64(*m), nil
26 }
27
28 var _ LevelMap = &mapLevelMap{}
29
30 // default constructor
31-func NewLevelMap() LevelMap {
32- return &mapLevelMap{}
33+func NewLevelMap() (LevelMap, error) {
34+ return &mapLevelMap{}, nil
35 }
36
37=== modified file 'client/session/levelmap/levelmap_test.go'
38--- client/session/levelmap/levelmap_test.go 2014-01-30 10:46:21 +0000
39+++ client/session/levelmap/levelmap_test.go 2014-02-12 13:24:35 +0000
40@@ -23,19 +23,33 @@
41
42 func TestLevelMap(t *testing.T) { TestingT(t) }
43
44-type lmSuite struct{}
45+type lmSuite struct {
46+ constructor func() (LevelMap, error)
47+}
48
49 var _ = Suite(&lmSuite{})
50
51-func (cs *lmSuite) TestAllTheThings(c *C) {
52+func (s *lmSuite) SetUpSuite(c *C) {
53+ s.constructor = NewLevelMap
54+}
55+
56+func (s *lmSuite) TestAllTheThings(c *C) {
57+ var err error
58+ var lm LevelMap
59 // checks NewLevelMap returns a LevelMap
60- var lm LevelMap = NewLevelMap()
61+ lm, err = s.constructor()
62+ // and that it works
63+ c.Assert(err, IsNil)
64 // setting a couple of things, sets them
65- lm.Set("this", 12)
66- lm.Set("that", 42)
67- c.Check(lm.GetAll(), DeepEquals, map[string]int64{"this": 12, "that": 42})
68+ c.Check(lm.Set("this", 12), IsNil)
69+ c.Check(lm.Set("that", 42), IsNil)
70+ all, err := lm.GetAll()
71+ c.Check(err, IsNil)
72+ c.Check(all, DeepEquals, map[string]int64{"this": 12, "that": 42})
73 // re-setting one of them, resets it
74- lm.Set("this", 999)
75- c.Check(lm.GetAll(), DeepEquals, map[string]int64{"this": 999, "that": 42})
76+ c.Check(lm.Set("this", 999), IsNil)
77+ all, err = lm.GetAll()
78+ c.Check(err, IsNil)
79+ c.Check(all, DeepEquals, map[string]int64{"this": 999, "that": 42})
80 // huzzah
81 }
82
83=== modified file 'client/session/session.go'
84--- client/session/session.go 2014-02-05 13:23:53 +0000
85+++ client/session/session.go 2014-02-12 13:24:35 +0000
86@@ -80,13 +80,17 @@
87 func NewSession(serverAddr string, pem []byte, exchangeTimeout time.Duration,
88 deviceId string, log logger.Logger) (*ClientSession, error) {
89 state := uint32(Disconnected)
90+ levels, err := levelmap.NewLevelMap()
91+ if err != nil {
92+ return nil, err
93+ }
94 sess := &ClientSession{
95 ExchangeTimeout: exchangeTimeout,
96 ServerAddr: serverAddr,
97 DeviceId: deviceId,
98 Log: log,
99 Protocolator: protocol.NewProtocol0,
100- Levels: levelmap.NewLevelMap(),
101+ Levels: levels,
102 TLS: &tls.Config{InsecureSkipVerify: true}, // XXX
103 stateP: &state,
104 }
105@@ -164,7 +168,16 @@
106
107 // handle "broadcast" messages
108 func (sess *ClientSession) handleBroadcast(bcast *serverMsg) error {
109- err := sess.proto.WriteMessage(protocol.AckMsg{"ack"})
110+ err := sess.Levels.Set(bcast.ChanId, bcast.TopLevel)
111+ if err != nil {
112+ sess.setState(Error)
113+ sess.Log.Errorf("unable to set level: %v", err)
114+ sess.proto.WriteMessage(protocol.AckMsg{"nak"})
115+ return err
116+ }
117+ // the server assumes if we ack the broadcast, we've updated
118+ // our levels. Hence the order.
119+ err = sess.proto.WriteMessage(protocol.AckMsg{"ack"})
120 if err != nil {
121 sess.setState(Error)
122 sess.Log.Errorf("unable to ack broadcast: %s", err)
123@@ -175,7 +188,6 @@
124 if bcast.ChanId == protocol.SystemChannelId {
125 // the system channel id, the only one we care about for now
126 sess.Log.Debugf("sending it over")
127- sess.Levels.Set(bcast.ChanId, bcast.TopLevel)
128 sess.MsgCh <- &Notification{}
129 sess.Log.Debugf("sent it over")
130 } else {
131@@ -228,10 +240,16 @@
132 }
133 proto := sess.Protocolator(conn)
134 proto.SetDeadline(time.Now().Add(sess.ExchangeTimeout))
135+ levels, err := sess.Levels.GetAll()
136+ if err != nil {
137+ sess.setState(Error)
138+ sess.Log.Errorf("unable to start: get levels: %v", err)
139+ return err
140+ }
141 err = proto.WriteMessage(protocol.ConnectMsg{
142 Type: "connect",
143 DeviceId: sess.DeviceId,
144- Levels: sess.Levels.GetAll(),
145+ Levels: levels,
146 })
147 if err != nil {
148 sess.setState(Error)
149
150=== modified file 'client/session/session_test.go'
151--- client/session/session_test.go 2014-02-05 18:23:47 +0000
152+++ client/session/session_test.go 2014-02-12 13:24:35 +0000
153@@ -156,6 +156,14 @@
154 return nil
155 }
156
157+// brokenLevelMap is a LevelMap that always breaks
158+type brokenLevelMap struct{}
159+
160+func (*brokenLevelMap) Set(string, int64) error { return errors.New("broken.") }
161+func (*brokenLevelMap) GetAll() (map[string]int64, error) { return nil, errors.New("broken.") }
162+
163+/////
164+
165 func (cs *clientSessionSuite) SetUpTest(c *C) {
166 cs.log = helpers.NewTestLogger(c, "debug")
167 }
168@@ -365,7 +373,9 @@
169 c.Assert(len(s.sess.MsgCh), Equals, 1)
170 c.Check(<-s.sess.MsgCh, Equals, &Notification{})
171 // and finally, the session keeps track of the levels
172- c.Check(s.sess.Levels.GetAll(), DeepEquals, map[string]int64{"0": 2})
173+ levels, err := s.sess.Levels.GetAll()
174+ c.Check(err, IsNil)
175+ c.Check(levels, DeepEquals, map[string]int64{"0": 2})
176 }
177
178 func (s *msgSuite) TestHandleBroadcastBadAckWrite(c *C) {
179@@ -401,6 +411,27 @@
180 c.Check(len(s.sess.MsgCh), Equals, 0)
181 }
182
183+func (s *msgSuite) TestHandleBroadcastWrongBrokenLevelmap(c *C) {
184+ s.sess.Levels = &brokenLevelMap{}
185+ msg := serverMsg{"broadcast",
186+ protocol.BroadcastMsg{
187+ Type: "broadcast",
188+ AppId: "--ignored--",
189+ ChanId: "0",
190+ TopLevel: 2,
191+ Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},
192+ }, protocol.NotificationsMsg{}}
193+ go func() { s.errCh <- s.sess.handleBroadcast(&msg) }()
194+ s.upCh <- nil // ack ok
195+ // start returns with error
196+ c.Check(<-s.errCh, Not(Equals), nil)
197+ // no message sent out
198+ c.Check(len(s.sess.MsgCh), Equals, 0)
199+ // and nak'ed it
200+ c.Check(len(s.downCh), Equals, 1)
201+ c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"nak"})
202+}
203+
204 /****************************************************************
205 loop() tests
206 ****************************************************************/
207@@ -488,6 +519,26 @@
208 c.Check(sess.State(), Equals, Error)
209 }
210
211+func (cs *clientSessionSuite) TestStartFailsIfGetLevelsFails(c *C) {
212+ sess, err := NewSession("", nil, 0, "wah", cs.log)
213+ c.Assert(err, IsNil)
214+ sess.Levels = &brokenLevelMap{}
215+ sess.Connection = &testConn{Name: "TestStartConnectMessageFails"}
216+ errCh := make(chan error, 1)
217+ upCh := make(chan interface{}, 5)
218+ downCh := make(chan interface{}, 5)
219+ proto := &testProtocol{up: upCh, down: downCh}
220+ sess.Protocolator = func(_ net.Conn) protocol.Protocol { return proto }
221+
222+ go func() {
223+ errCh <- sess.start()
224+ }()
225+
226+ c.Check(takeNext(downCh), Equals, "deadline 0")
227+ err = <-errCh
228+ c.Check(err, ErrorMatches, "broken.")
229+}
230+
231 func (cs *clientSessionSuite) TestStartConnectMessageFails(c *C) {
232 sess, err := NewSession("", nil, 0, "wah", cs.log)
233 c.Assert(err, IsNil)
234@@ -788,7 +839,9 @@
235 // ...get bubbled up,
236 c.Check(<-sess.MsgCh, NotNil)
237 // and their TopLevel remembered
238- c.Check(sess.Levels.GetAll(), DeepEquals, map[string]int64{"0": 2})
239+ levels, err := sess.Levels.GetAll()
240+ c.Check(err, IsNil)
241+ c.Check(levels, DeepEquals, map[string]int64{"0": 2})
242
243 // and ping still work even after that.
244 c.Check(takeNext(downCh), Equals, "deadline 110ms")

Subscribers

People subscribed via source and target branches