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
=== modified file 'magicicada/dbusiface.py'
--- magicicada/dbusiface.py 2010-05-31 16:45:45 +0000
+++ magicicada/dbusiface.py 2010-06-04 02:47:22 +0000
@@ -26,6 +26,7 @@
26import dbus26import dbus
27from dbus import SessionBus27from dbus import SessionBus
28from dbus.mainloop.glib import DBusGMainLoop28from dbus.mainloop.glib import DBusGMainLoop
29from twisted.internet import defer
2930
30from ubuntuone.syncdaemon.tools import SyncDaemonTool31from ubuntuone.syncdaemon.tools import SyncDaemonTool
3132
@@ -59,6 +60,34 @@
59 "new_parent_id=(.*?), new_name=(.*?)\)")60 "new_parent_id=(.*?), new_name=(.*?)\)")
6061
6162
63def _is_retry_exception(err):
64 """Check if the exception is a retry one."""
65 if isinstance(err, dbus.exceptions.DBusException):
66 if err.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
67 return True
68 return False
69
70def retryable(func):
71 """Call the function until its deferred succeed (max 5 times)."""
72
73 @defer.inlineCallbacks
74 def f(*a, **k):
75 """Built func."""
76 opportunities = 10
77 while opportunities:
78 try:
79 res = yield func(*a, **k)
80 except Exception, err:
81 opportunities -= 1
82 if opportunities == 0 or not _is_retry_exception(err):
83 raise
84 else:
85 break
86 defer.returnValue(res)
87
88 return f
89
90
62class DBusInterface(object):91class DBusInterface(object):
63 """The DBus Interface to Ubuntu One's SyncDaemon."""92 """The DBus Interface to Ubuntu One's SyncDaemon."""
6493
@@ -116,8 +145,9 @@
116 return (name, description, is_error, is_connected,145 return (name, description, is_error, is_connected,
117 is_online, queues, connection)146 is_online, queues, connection)
118147
148 @retryable
119 def get_status(self):149 def get_status(self):
120 """Gets SD status."""150 """Get SD status."""
121 logger.info("Getting status")151 logger.info("Getting status")
122 d = self.sync_daemon_tool.get_status()152 d = self.sync_daemon_tool.get_status()
123 d.addCallback(self._process_status)153 d.addCallback(self._process_status)
@@ -174,6 +204,7 @@
174 logger.info("Received Share changed")204 logger.info("Received Share changed")
175 self.msd.on_sd_shares_changed()205 self.msd.on_sd_shares_changed()
176206
207 @retryable
177 def get_content_queue(self):208 def get_content_queue(self):
178 """Get the content queue from SDT."""209 """Get the content queue from SDT."""
179 def process(data):210 def process(data):
@@ -237,6 +268,7 @@
237268
238 raise ValueError("Not supported MetaQueue data: %r" % data)269 raise ValueError("Not supported MetaQueue data: %r" % data)
239270
271 @retryable
240 def get_meta_queue(self):272 def get_meta_queue(self):
241 """Get the meta queue from SDT."""273 """Get the meta queue from SDT."""
242 def process(data):274 def process(data):
@@ -254,6 +286,7 @@
254 d.addCallback(process)286 d.addCallback(process)
255 return d287 return d
256288
289 @retryable
257 def get_folders(self):290 def get_folders(self):
258 """Get the folders info from SDT."""291 """Get the folders info from SDT."""
259 def process(data):292 def process(data):
@@ -309,7 +342,7 @@
309 return started342 return started
310343
311 def _process_share_info(self, data):344 def _process_share_info(self, data):
312 """Processes share data."""345 """Process share data."""
313 all_items = []346 all_items = []
314 for d in data:347 for d in data:
315 logger.debug(" Share data: %r", d)348 logger.debug(" Share data: %r", d)
@@ -332,6 +365,7 @@
332 all_items.append(s)365 all_items.append(s)
333 return all_items366 return all_items
334367
368 @retryable
335 def get_shares_to_me(self):369 def get_shares_to_me(self):
336 """Get the shares to me ('shares') info from SDT."""370 """Get the shares to me ('shares') info from SDT."""
337 def process(data):371 def process(data):
@@ -344,6 +378,7 @@
344 d.addCallback(process)378 d.addCallback(process)
345 return d379 return d
346380
381 @retryable
347 def get_shares_to_others(self):382 def get_shares_to_others(self):
348 """Get the shares to others ('shared') info from SDT."""383 """Get the shares to others ('shared') info from SDT."""
349 def process(data):384 def process(data):
@@ -355,4 +390,3 @@
355 d = self.sync_daemon_tool.list_shared()390 d = self.sync_daemon_tool.list_shared()
356 d.addCallback(process)391 d.addCallback(process)
357 return d392 return d
358
359393
=== modified file 'magicicada/syncdaemon.py'
--- magicicada/syncdaemon.py 2010-05-31 00:31:17 +0000
+++ magicicada/syncdaemon.py 2010-06-04 02:47:22 +0000
@@ -38,7 +38,7 @@
3838
3939
40class State(object):40class State(object):
41 """Holds the state of SD."""41 """Hold the state of SD."""
42 _attrs = ['name', 'description', 'is_error', 'is_connected',42 _attrs = ['name', 'description', 'is_error', 'is_connected',
43 'is_online', 'queues', 'connection', 'is_started']43 'is_online', 'queues', 'connection', 'is_started']
4444
@@ -61,7 +61,7 @@
61 return self.__dict__[name]61 return self.__dict__[name]
6262
63 def _set(self, **data):63 def _set(self, **data):
64 """Sets the attributes fromd data, if allowed."""64 """Set the attributes from data, if allowed."""
65 for name, value in data.iteritems():65 for name, value in data.iteritems():
66 if name not in self._attrs:66 if name not in self._attrs:
67 raise AttributeError("Name not in _attrs: %r" % name)67 raise AttributeError("Name not in _attrs: %r" % name)
@@ -129,7 +129,7 @@
129129
130 @defer.inlineCallbacks130 @defer.inlineCallbacks
131 def _get_initial_data(self):131 def _get_initial_data(self):
132 """Gets the initial SD data."""132 """Get the initial SD data."""
133 logger.info("Getting initial data")133 logger.info("Getting initial data")
134134
135 status_data = yield self.dbus.get_status()135 status_data = yield self.dbus.get_status()
@@ -259,22 +259,22 @@
259 self._check_mq)259 self._check_mq)
260260
261 def start(self):261 def start(self):
262 """Starts the SyncDaemon."""262 """Start the SyncDaemon."""
263 logger.info("Starting u1.SD")263 logger.info("Starting u1.SD")
264 self.dbus.start()264 self.dbus.start()
265 self._get_initial_data()265 self._get_initial_data()
266266
267 def quit(self):267 def quit(self):
268 """Stops the SyncDaemon and makes it quit."""268 """Stop the SyncDaemon and makes it quit."""
269 logger.info("Stopping u1.SD")269 logger.info("Stopping u1.SD")
270 self.dbus.quit()270 self.dbus.quit()
271271
272 def connect(self):272 def connect(self):
273 """Tells the SyncDaemon that the user wants it to connect."""273 """Tell the SyncDaemon that the user wants it to connect."""
274 logger.info("Telling u1.SD to connect")274 logger.info("Telling u1.SD to connect")
275 self.dbus.connect()275 self.dbus.connect()
276276
277 def disconnect(self):277 def disconnect(self):
278 """Tells the SyncDaemon that the user wants it to disconnect."""278 """Tell the SyncDaemon that the user wants it to disconnect."""
279 logger.info("Telling u1.SD to disconnect")279 logger.info("Telling u1.SD to disconnect")
280 self.dbus.disconnect()280 self.dbus.disconnect()
281281
=== modified file 'magicicada/tests/test_dbusiface.py'
--- magicicada/tests/test_dbusiface.py 2010-05-31 16:45:45 +0000
+++ magicicada/tests/test_dbusiface.py 2010-06-04 02:47:22 +0000
@@ -871,3 +871,110 @@
871 self.assertTrue(self.handler.check_inf(871 self.assertTrue(self.handler.check_inf(
872 "Processing Shares To Others items (1)"))872 "Processing Shares To Others items (1)"))
873 self.assertTrue(self.handler.check_dbg(" Share data: %r" % d))873 self.assertTrue(self.handler.check_dbg(" Share data: %r" % d))
874
875
876class RetryDecoratorTests(TwistedTestCase):
877 """Test the retry decorator."""
878
879 class Helper(object):
880 """Fails some times, finally succeeds."""
881 def __init__(self, limit, excep=None):
882 self.cant = 0
883 self.limit = limit
884 if excep is None:
885 self.excep = dbus.exceptions.DBusException(
886 name='org.freedesktop.DBus.Error.NoReply')
887 else:
888 self.excep = excep
889
890 def __call__(self):
891 """Called."""
892 self.cant += 1
893 if self.cant < self.limit:
894 return defer.fail(self.excep)
895 else:
896 return defer.succeed(True)
897
898 def test_retryexcep_noexcep(self):
899 """Test _is_retry_exception with no error."""
900 self.assertFalse(dbusiface._is_retry_exception("foo"))
901
902 def test_retryexcep_stdexcep(self):
903 """Test _is_retry_exception with a standard exception."""
904 self.assertFalse(dbusiface._is_retry_exception(NameError("foo")))
905
906 def test_retryexcep_dbusnoretry(self):
907 """Test _is_retry_exception with DBus exception, but not retry."""
908 err = dbus.exceptions.DBusException(name='org.freedesktop.DBus.Other')
909 self.assertFalse(dbusiface._is_retry_exception(err))
910
911 def test_retryexcep_dbusretry(self):
912 """Test _is_retry_exception with DBus exception, retry."""
913 err = dbus.exceptions.DBusException(
914 name='org.freedesktop.DBus.Error.NoReply')
915 self.assertTrue(dbusiface._is_retry_exception(err))
916
917 def get_decorated_func(self, func):
918 """Executes the test calling the received function."""
919
920 @dbusiface.retryable
921 def f():
922 """Test func."""
923 d = func()
924 return d
925
926 return f
927
928 def test_all_ok(self):
929 """All ok."""
930 f = self.get_decorated_func(lambda: defer.succeed(True))
931 d = f()
932 return d
933
934 def test_one_fail(self):
935 """One fail."""
936 deferred = defer.Deferred()
937 helper = self.Helper(2)
938 d = self.get_decorated_func(helper)()
939
940 def check(_):
941 """Check called quantity."""
942 self.assertEqual(helper.cant, 2)
943 deferred.callback(True)
944 d.addCallbacks(check,
945 lambda _: deferred.errback(Exception()))
946 return deferred
947
948 def test_two_fails(self):
949 """Two fails."""
950 deferred = defer.Deferred()
951 helper = self.Helper(3)
952 d = self.get_decorated_func(helper)()
953
954 def check(_):
955 """Check called quantity."""
956 self.assertEqual(helper.cant, 3)
957 deferred.callback(True)
958 d.addCallbacks(check,
959 lambda _: deferred.errback(Exception()))
960 return deferred
961
962 def test_too_many_fails(self):
963 """Check that retryal is not forever."""
964 deferred = defer.Deferred()
965 helper = self.Helper(12)
966 d = self.get_decorated_func(helper)()
967
968 d.addCallbacks(lambda _: deferred.errback(Exception()),
969 lambda _: deferred.callback(True))
970 return deferred
971
972 def test_other_exception(self):
973 """Less fails than limit, but not retrying exception."""
974 deferred = defer.Deferred()
975 helper = self.Helper(2, excep=NameError("foo"))
976 d = self.get_decorated_func(helper)()
977
978 d.addCallbacks(lambda _: deferred.errback(Exception()),
979 lambda _: deferred.callback(True))
980 return deferred

Subscribers

People subscribed via source and target branches