Merge lp:~mandel/ubuntuone-client/stale-broker into lp:ubuntuone-client

Proposed by Manuel de la Peña
Status: Merged
Approved by: Mike McCracken
Approved revision: 1297
Merged at revision: 1299
Proposed branch: lp:~mandel/ubuntuone-client/stale-broker
Merge into: lp:ubuntuone-client
Diff against target: 200 lines (+113/-3)
3 files modified
tests/platform/tools/test_tools.py (+64/-0)
ubuntuone/platform/ipc/ipc_client.py (+12/-0)
ubuntuone/platform/tools/perspective_broker.py (+37/-3)
To merge this branch: bzr merge lp:~mandel/ubuntuone-client/stale-broker
Reviewer Review Type Date Requested Status
Mike McCracken (community) Approve
Roberto Alsina (community) Approve
Review via email: mp+121456@code.launchpad.net

Commit message

- Fix stale broker problems by reconnecting to the server and requesting new valid instances for the reference objects in the client side (LP: #1040915).

Description of the change

- Fix stale broker problems by reconnecting to the server and requesting new valid instances for the reference objects in the client side (LP: #1040915).

To post a comment you must log in.
Revision history for this message
Mike McCracken (mikemc) wrote :

As discussed on IRC, I'd like to understand a bit more about why we're disconnecting and getting invalid refs before we just auto-reconnect on failures.

The bug seems to come up when switching between tabs, and we might (didn't confirm) have one PB connection per tab to the syncdaemon - is something locally getting GC'd when a tab isn't visible?
If so, why does that cause a remote ref to be invalid? I haven't seen any evidence that syncdaemon itself is dying.

review: Needs Information
Revision history for this message
Roberto Alsina (ralsina) wrote :

Works ok for me. While we do want to know why we are reconnecting, reconnection in itself is useful (for example, because of sd crashes)

review: Approve
Revision history for this message
Mike McCracken (mikemc) wrote :

OK, the branch works for me, so I'll approve.

Filed bug #1042789 to look into the root cause

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'tests/platform/tools/test_tools.py'
2--- tests/platform/tools/test_tools.py 2012-05-24 15:09:34 +0000
3+++ tests/platform/tools/test_tools.py 2012-08-27 16:04:01 +0000
4@@ -32,6 +32,7 @@
5 import operator
6
7 from twisted.internet import defer
8+from twisted.spread import pb
9 from twisted.trial.unittest import TestCase
10
11 from ubuntuone.platform.tools import perspective_broker
12@@ -125,3 +126,66 @@
13 attr = getattr(self.sdtool, attr_name)
14 func_name = getattr(attr, "__name__", None)
15 self.assertNotEqual(func_name, "call_after_connection_inner")
16+
17+
18+class FakeRemoteObject(object):
19+ """Fake a remote object."""
20+
21+ def __init__(self):
22+ """Create a new instance."""
23+ self.number_calls = 0
24+ self.called = []
25+
26+ def method_call(self, *args, **kwargs):
27+ """Fake a remote method call."""
28+ if self.number_calls == 0:
29+ self.number_calls += 1
30+ raise pb.DeadReferenceError()
31+ else:
32+ self.called.append((args, kwargs))
33+ return defer.succeed(self.number_calls)
34+
35+
36+class PerspectiveBrokerReconnect(TestCase):
37+ """Test when the ipc is reconnected."""
38+
39+ @defer.inlineCallbacks
40+ def setUp(self):
41+ """Set the tests."""
42+ yield super(PerspectiveBrokerReconnect, self).setUp()
43+ self.sdtool = perspective_broker.SyncDaemonToolProxy()
44+ self.sdtool.client.fake_remote = FakeRemoteObject()
45+ self.connected_signals = []
46+ self.reconnected = False
47+
48+ def connect_signal(my_self, *args, **kwargs):
49+ """Fake connect_signal call."""
50+ self.connected_signals.append(('connect_signal', args, kwargs))
51+
52+ self.patch(perspective_broker.SyncDaemonToolProxy, 'connect_signal',
53+ connect_signal)
54+
55+ def fake_reconnect(_):
56+ """Fake the reconnection of the client."""
57+ self.reconnected = True
58+
59+ self.patch(perspective_broker.UbuntuOneClient, 'reconnect',
60+ fake_reconnect)
61+ self.patch(perspective_broker.UbuntuOneClient, 'connect',
62+ lambda _: defer.succeed(True))
63+
64+ @defer.inlineCallbacks
65+ def test_reconnect_no_signals(self):
66+ """Test reconnection with no signals."""
67+ yield self.sdtool.call_method('fake_remote', 'method_call')
68+ self.assertTrue(self.reconnected)
69+ self.assertEqual(0, len(self.connected_signals))
70+
71+ @defer.inlineCallbacks
72+ def test_reconnect_signals(self):
73+ """Test reconnection with signals."""
74+ self.sdtool.connected_signals = dict(create_signal=lambda:None,
75+ delete_signal=lambda:None)
76+ yield self.sdtool.call_method('fake_remote', 'method_call')
77+ self.assertTrue(self.reconnected)
78+ self.assertEqual(2, len(self.connected_signals))
79
80=== modified file 'ubuntuone/platform/ipc/ipc_client.py'
81--- ubuntuone/platform/ipc/ipc_client.py 2012-08-16 12:00:12 +0000
82+++ ubuntuone/platform/ipc/ipc_client.py 2012-08-27 16:04:01 +0000
83@@ -778,6 +778,18 @@
84 'Could not connect to the syncdaemon ipc.', e)
85 # pylint: disable=W0702
86
87+ @defer.inlineCallbacks
88+ def reconnect(self):
89+ """Reconnect and get the new remote objects."""
90+ try:
91+ root = yield self.factory.getRootObject()
92+ yield self._request_remote_objects(root)
93+ yield self.register_to_signals()
94+ defer.returnValue(self)
95+ except Exception, e:
96+ raise SyncDaemonClientConnectionError(
97+ 'Could not reconnect to the syncdaemon ipc.', e)
98+
99 def is_connected(self):
100 """Return if the client is connected."""
101 return (self.client is not None)
102
103=== modified file 'ubuntuone/platform/tools/perspective_broker.py'
104--- ubuntuone/platform/tools/perspective_broker.py 2012-07-13 16:06:27 +0000
105+++ ubuntuone/platform/tools/perspective_broker.py 2012-08-27 16:04:01 +0000
106@@ -28,9 +28,11 @@
107 # files in the program, then also delete it here.
108 """SyncDaemon Tools."""
109
110+import logging
111 import subprocess
112
113 from twisted.internet import defer
114+from twisted.spread.pb import DeadReferenceError
115
116 from ubuntuone.platform.ipc.perspective_broker import is_already_running
117 from ubuntuone.platform.ipc.ipc_client import UbuntuOneClient
118@@ -99,6 +101,7 @@
119 _DONT_VERIFY_CONNECTED = [
120 "wait_connected",
121 "client", "last_event", "delayed_call", "log", "connected",
122+ "connected_signals"
123 ]
124
125 def _should_wrap(self, attr_name):
126@@ -115,8 +118,11 @@
127 return attr
128
129 def __init__(self, bus=None):
130+ self.log = logging.getLogger('ubuntuone.platform.tools.' +
131+ 'perspective_broker')
132 self.client = UbuntuOneClient()
133 self.connected = None
134+ self.connected_signals = {}
135
136 def _call_after_connection(self, method):
137 """Make sure Perspective Broker is connected before calling."""
138@@ -127,17 +133,41 @@
139 def call_after_connection_inner(*args, **kwargs):
140 """Call the given method after the connection to pb is made."""
141 yield self.connected
142- retval = yield method(*args, **kwargs)
143+ try:
144+ retval = yield method(*args, **kwargs)
145+ except DeadReferenceError:
146+ self.log.debug('Got stale broker, atempting reconnect.')
147+ # might be the case where we have a stale broker
148+ yield self._reconnect_client()
149+ retval = yield method(*args, **kwargs)
150 defer.returnValue(retval)
151
152 return call_after_connection_inner
153
154+ @defer.inlineCallbacks
155+ def _reconnect_client(self):
156+ """Reconnect the client."""
157+ self.connected = False
158+ yield self.client.reconnect()
159+ # do connect all the signals again
160+ for signal_name, handler in self.connected_signals.items():
161+ self.connect_signal(signal_name, handler)
162+
163+ @defer.inlineCallbacks
164 def call_method(self, client_kind, method_name, *args, **kwargs):
165 """Call the 'method_name' passing 'args' and 'kwargs'."""
166 client = getattr(self.client, client_kind)
167 method = getattr(client, method_name)
168- result = method(*args, **kwargs)
169- return result
170+ try:
171+ result = yield method(*args, **kwargs)
172+ except DeadReferenceError:
173+ self.log.debug('Got stale broker, atempting reconnect.')
174+ # may happen in the case we reconnected and the server side objects
175+ # for gc
176+ yield self._reconnect_client()
177+ result = yield self.call_method(client_kind, method_name,
178+ *args, **kwargs)
179+ defer.returnValue(result)
180
181 def shutdown(self):
182 """Close connections."""
183@@ -148,6 +178,8 @@
184 client_kind, callback = self._SIGNAL_MAPPING[signal_name]
185 client = getattr(self.client, client_kind)
186 setattr(client, callback, handler)
187+ # do remember the connected signal in case we need to reconnect
188+ self.connected_signals[signal_name] = handler
189 return handler
190
191 def disconnect_signal(self, signal_name, handler_or_match):
192@@ -155,6 +187,8 @@
193 client_kind, callback = self._SIGNAL_MAPPING[signal_name]
194 client = getattr(self.client, client_kind)
195 setattr(client, callback, None)
196+ # forget that the connection was made in case we need to reconnect
197+ del self.connected_signals[signal_name]
198 return handler_or_match
199
200 def wait_connected(self):

Subscribers

People subscribed via source and target branches