Merge lp:~facundo/magicicada-gui/retry-dbus into lp:magicicada-gui

Proposed by Facundo Batista
Status: Merged
Approved by: Natalia Bidart
Approved revision: 38
Merged at revision: 39
Proposed branch: lp:~facundo/magicicada-gui/retry-dbus
Merge into: lp:magicicada-gui
Diff against target: 284 lines (+151/-10)
3 files modified
magicicada/dbusiface.py (+37/-3)
magicicada/syncdaemon.py (+7/-7)
magicicada/tests/test_dbusiface.py (+107/-0)
To merge this branch: bzr merge lp:~facundo/magicicada-gui/retry-dbus
Reviewer Review Type Date Requested Status
Natalia Bidart Approve
Review via email: mp+26663@code.launchpad.net

Description of the change

DBus calls are retryable now!

To post a comment you must log in.
38. By Facundo Batista

Removed debugging prints, and fixed some docstrings.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Perfect!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'magicicada/dbusiface.py'
2--- magicicada/dbusiface.py 2010-05-31 16:45:45 +0000
3+++ magicicada/dbusiface.py 2010-06-04 02:47:22 +0000
4@@ -26,6 +26,7 @@
5 import dbus
6 from dbus import SessionBus
7 from dbus.mainloop.glib import DBusGMainLoop
8+from twisted.internet import defer
9
10 from ubuntuone.syncdaemon.tools import SyncDaemonTool
11
12@@ -59,6 +60,34 @@
13 "new_parent_id=(.*?), new_name=(.*?)\)")
14
15
16+def _is_retry_exception(err):
17+ """Check if the exception is a retry one."""
18+ if isinstance(err, dbus.exceptions.DBusException):
19+ if err.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
20+ return True
21+ return False
22+
23+def retryable(func):
24+ """Call the function until its deferred succeed (max 5 times)."""
25+
26+ @defer.inlineCallbacks
27+ def f(*a, **k):
28+ """Built func."""
29+ opportunities = 10
30+ while opportunities:
31+ try:
32+ res = yield func(*a, **k)
33+ except Exception, err:
34+ opportunities -= 1
35+ if opportunities == 0 or not _is_retry_exception(err):
36+ raise
37+ else:
38+ break
39+ defer.returnValue(res)
40+
41+ return f
42+
43+
44 class DBusInterface(object):
45 """The DBus Interface to Ubuntu One's SyncDaemon."""
46
47@@ -116,8 +145,9 @@
48 return (name, description, is_error, is_connected,
49 is_online, queues, connection)
50
51+ @retryable
52 def get_status(self):
53- """Gets SD status."""
54+ """Get SD status."""
55 logger.info("Getting status")
56 d = self.sync_daemon_tool.get_status()
57 d.addCallback(self._process_status)
58@@ -174,6 +204,7 @@
59 logger.info("Received Share changed")
60 self.msd.on_sd_shares_changed()
61
62+ @retryable
63 def get_content_queue(self):
64 """Get the content queue from SDT."""
65 def process(data):
66@@ -237,6 +268,7 @@
67
68 raise ValueError("Not supported MetaQueue data: %r" % data)
69
70+ @retryable
71 def get_meta_queue(self):
72 """Get the meta queue from SDT."""
73 def process(data):
74@@ -254,6 +286,7 @@
75 d.addCallback(process)
76 return d
77
78+ @retryable
79 def get_folders(self):
80 """Get the folders info from SDT."""
81 def process(data):
82@@ -309,7 +342,7 @@
83 return started
84
85 def _process_share_info(self, data):
86- """Processes share data."""
87+ """Process share data."""
88 all_items = []
89 for d in data:
90 logger.debug(" Share data: %r", d)
91@@ -332,6 +365,7 @@
92 all_items.append(s)
93 return all_items
94
95+ @retryable
96 def get_shares_to_me(self):
97 """Get the shares to me ('shares') info from SDT."""
98 def process(data):
99@@ -344,6 +378,7 @@
100 d.addCallback(process)
101 return d
102
103+ @retryable
104 def get_shares_to_others(self):
105 """Get the shares to others ('shared') info from SDT."""
106 def process(data):
107@@ -355,4 +390,3 @@
108 d = self.sync_daemon_tool.list_shared()
109 d.addCallback(process)
110 return d
111-
112
113=== modified file 'magicicada/syncdaemon.py'
114--- magicicada/syncdaemon.py 2010-05-31 00:31:17 +0000
115+++ magicicada/syncdaemon.py 2010-06-04 02:47:22 +0000
116@@ -38,7 +38,7 @@
117
118
119 class State(object):
120- """Holds the state of SD."""
121+ """Hold the state of SD."""
122 _attrs = ['name', 'description', 'is_error', 'is_connected',
123 'is_online', 'queues', 'connection', 'is_started']
124
125@@ -61,7 +61,7 @@
126 return self.__dict__[name]
127
128 def _set(self, **data):
129- """Sets the attributes fromd data, if allowed."""
130+ """Set the attributes from data, if allowed."""
131 for name, value in data.iteritems():
132 if name not in self._attrs:
133 raise AttributeError("Name not in _attrs: %r" % name)
134@@ -129,7 +129,7 @@
135
136 @defer.inlineCallbacks
137 def _get_initial_data(self):
138- """Gets the initial SD data."""
139+ """Get the initial SD data."""
140 logger.info("Getting initial data")
141
142 status_data = yield self.dbus.get_status()
143@@ -259,22 +259,22 @@
144 self._check_mq)
145
146 def start(self):
147- """Starts the SyncDaemon."""
148+ """Start the SyncDaemon."""
149 logger.info("Starting u1.SD")
150 self.dbus.start()
151 self._get_initial_data()
152
153 def quit(self):
154- """Stops the SyncDaemon and makes it quit."""
155+ """Stop the SyncDaemon and makes it quit."""
156 logger.info("Stopping u1.SD")
157 self.dbus.quit()
158
159 def connect(self):
160- """Tells the SyncDaemon that the user wants it to connect."""
161+ """Tell the SyncDaemon that the user wants it to connect."""
162 logger.info("Telling u1.SD to connect")
163 self.dbus.connect()
164
165 def disconnect(self):
166- """Tells the SyncDaemon that the user wants it to disconnect."""
167+ """Tell the SyncDaemon that the user wants it to disconnect."""
168 logger.info("Telling u1.SD to disconnect")
169 self.dbus.disconnect()
170
171=== modified file 'magicicada/tests/test_dbusiface.py'
172--- magicicada/tests/test_dbusiface.py 2010-05-31 16:45:45 +0000
173+++ magicicada/tests/test_dbusiface.py 2010-06-04 02:47:22 +0000
174@@ -871,3 +871,110 @@
175 self.assertTrue(self.handler.check_inf(
176 "Processing Shares To Others items (1)"))
177 self.assertTrue(self.handler.check_dbg(" Share data: %r" % d))
178+
179+
180+class RetryDecoratorTests(TwistedTestCase):
181+ """Test the retry decorator."""
182+
183+ class Helper(object):
184+ """Fails some times, finally succeeds."""
185+ def __init__(self, limit, excep=None):
186+ self.cant = 0
187+ self.limit = limit
188+ if excep is None:
189+ self.excep = dbus.exceptions.DBusException(
190+ name='org.freedesktop.DBus.Error.NoReply')
191+ else:
192+ self.excep = excep
193+
194+ def __call__(self):
195+ """Called."""
196+ self.cant += 1
197+ if self.cant < self.limit:
198+ return defer.fail(self.excep)
199+ else:
200+ return defer.succeed(True)
201+
202+ def test_retryexcep_noexcep(self):
203+ """Test _is_retry_exception with no error."""
204+ self.assertFalse(dbusiface._is_retry_exception("foo"))
205+
206+ def test_retryexcep_stdexcep(self):
207+ """Test _is_retry_exception with a standard exception."""
208+ self.assertFalse(dbusiface._is_retry_exception(NameError("foo")))
209+
210+ def test_retryexcep_dbusnoretry(self):
211+ """Test _is_retry_exception with DBus exception, but not retry."""
212+ err = dbus.exceptions.DBusException(name='org.freedesktop.DBus.Other')
213+ self.assertFalse(dbusiface._is_retry_exception(err))
214+
215+ def test_retryexcep_dbusretry(self):
216+ """Test _is_retry_exception with DBus exception, retry."""
217+ err = dbus.exceptions.DBusException(
218+ name='org.freedesktop.DBus.Error.NoReply')
219+ self.assertTrue(dbusiface._is_retry_exception(err))
220+
221+ def get_decorated_func(self, func):
222+ """Executes the test calling the received function."""
223+
224+ @dbusiface.retryable
225+ def f():
226+ """Test func."""
227+ d = func()
228+ return d
229+
230+ return f
231+
232+ def test_all_ok(self):
233+ """All ok."""
234+ f = self.get_decorated_func(lambda: defer.succeed(True))
235+ d = f()
236+ return d
237+
238+ def test_one_fail(self):
239+ """One fail."""
240+ deferred = defer.Deferred()
241+ helper = self.Helper(2)
242+ d = self.get_decorated_func(helper)()
243+
244+ def check(_):
245+ """Check called quantity."""
246+ self.assertEqual(helper.cant, 2)
247+ deferred.callback(True)
248+ d.addCallbacks(check,
249+ lambda _: deferred.errback(Exception()))
250+ return deferred
251+
252+ def test_two_fails(self):
253+ """Two fails."""
254+ deferred = defer.Deferred()
255+ helper = self.Helper(3)
256+ d = self.get_decorated_func(helper)()
257+
258+ def check(_):
259+ """Check called quantity."""
260+ self.assertEqual(helper.cant, 3)
261+ deferred.callback(True)
262+ d.addCallbacks(check,
263+ lambda _: deferred.errback(Exception()))
264+ return deferred
265+
266+ def test_too_many_fails(self):
267+ """Check that retryal is not forever."""
268+ deferred = defer.Deferred()
269+ helper = self.Helper(12)
270+ d = self.get_decorated_func(helper)()
271+
272+ d.addCallbacks(lambda _: deferred.errback(Exception()),
273+ lambda _: deferred.callback(True))
274+ return deferred
275+
276+ def test_other_exception(self):
277+ """Less fails than limit, but not retrying exception."""
278+ deferred = defer.Deferred()
279+ helper = self.Helper(2, excep=NameError("foo"))
280+ d = self.get_decorated_func(helper)()
281+
282+ d.addCallbacks(lambda _: deferred.errback(Exception()),
283+ lambda _: deferred.callback(True))
284+ return deferred

Subscribers

People subscribed via source and target branches