Rev 4885: Merge 2.1-client-read-reconnect-819604 in http://bazaar.launchpad.net/~jameinel/bzr/2.1-all-reconnect-819604

John Arbash Meinel john at arbash-meinel.com
Wed Sep 12 07:07:52 UTC 2012


At http://bazaar.launchpad.net/~jameinel/bzr/2.1-all-reconnect-819604

------------------------------------------------------------
revno: 4885 [merge]
revision-id: john at arbash-meinel.com-20120912070732-8xh7kllfxa6qr3ax
parent: john at arbash-meinel.com-20120912070702-frcohrl82jacgvq1
parent: john at arbash-meinel.com-20111010130807-jg97k047bl5d0x76
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: 2.1-all-reconnect-819604
timestamp: Wed 2012-09-12 11:07:32 +0400
message:
  Merge 2.1-client-read-reconnect-819604
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/smart/client.py         client.py-20061116014825-2k6ada6xgulslami-1
  bzrlib/tests/test_smart_transport.py test_ssh_transport.py-20060608202016-c25gvf1ob7ypbus6-2
-------------- next part --------------
=== modified file 'NEWS'
--- a/NEWS	2012-02-02 14:08:45 +0000
+++ b/NEWS	2012-09-12 07:07:32 +0000
@@ -43,6 +43,11 @@
 
   (John Arbash Meinel, #609187, #812928)
 
+* Teach the bzr client how to reconnect if we get ``ConnectionReset``
+  while making an RPC request. This doesn't handle all possible network
+  disconnects, but it should at least handle when the server is asked to
+  shutdown gracefully. (John Arbash Meinel, #819604)
+
 
 Improvements
 ************

=== modified file 'bzrlib/smart/client.py'
--- a/bzrlib/smart/client.py	2011-10-10 12:49:14 +0000
+++ b/bzrlib/smart/client.py	2011-10-10 13:04:31 +0000
@@ -14,6 +14,11 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
+from bzrlib import lazy_import
+lazy_import.lazy_import(globals(), """
+from bzrlib.smart import request as _mod_request
+""")
+
 import bzrlib
 from bzrlib.smart import message, protocol
 from bzrlib import (
@@ -149,6 +154,22 @@
         else:
             return self._call(protocol_version)
 
+    def _is_safe_to_send_twice(self):
+        """Check if the current method is re-entrant safe."""
+        if self.body_stream is not None or 'noretry' in debug.debug_flags:
+            # We can't restart a body stream that has already been consumed.
+            return False
+        request_type = _mod_request.request_handlers.get_info(self.method)
+        if request_type in ('read', 'idem', 'semi'):
+            return True
+        # If we have gotten this far, 'stream' cannot be retried, because we
+        # already consumed the local stream.
+        if request_type in ('semivfs', 'mutate', 'stream'):
+            return False
+        trace.mutter('Unknown request type: %s for method %s'
+                     % (request_type, self.method))
+        return False
+
     def _run_call_hooks(self):
         if not _SmartClient.hooks['call']:
             return
@@ -164,8 +185,21 @@
         where the code will be to retry requests if the connection is closed.
         """
         response_handler = self._send(protocol_version)
-        response_tuple = response_handler.read_response_tuple(
-            expect_body=self.expect_response_body)
+        try:
+            response_tuple = response_handler.read_response_tuple(
+                expect_body=self.expect_response_body)
+        except errors.ConnectionReset, e:
+            self.client._medium.reset()
+            if not self._is_safe_to_send_twice():
+                raise
+            trace.warning('ConnectionReset reading response for %r, retrying'
+                          % (self.method,))
+            trace.log_exception_quietly()
+            encoder, response_handler = self._construct_protocol(
+                protocol_version)
+            self._send_no_retry(encoder)
+            response_tuple = response_handler.read_response_tuple(
+                expect_body=self.expect_response_body)
         return (response_tuple, response_handler)
 
     def _call_determining_protocol_version(self):

=== modified file 'bzrlib/tests/test_smart_transport.py'
--- a/bzrlib/tests/test_smart_transport.py	2011-10-10 12:49:14 +0000
+++ b/bzrlib/tests/test_smart_transport.py	2011-10-10 13:00:28 +0000
@@ -3382,10 +3382,10 @@
 
 class Test_SmartClientRequest(tests.TestCase):
 
-    def make_client_with_failing_medium(self, fail_at_write=True):
-        response = StringIO()
+    def make_client_with_failing_medium(self, fail_at_write=True, response=''):
+        response_io = StringIO(response)
         output = StringIO()
-        vendor = FirstRejectedStringIOSSHVendor(response, output,
+        vendor = FirstRejectedStringIOSSHVendor(response_io, output,
                     fail_at_write=fail_at_write)
         client_medium = medium.SmartSSHClientMedium(
             'a host', 'a port', 'a user', 'a pass', 'base', vendor,
@@ -3393,6 +3393,41 @@
         smart_client = client._SmartClient(client_medium, headers={})
         return output, vendor, smart_client
 
+    def make_response(self, args, body=None, body_stream=None):
+        response_io = StringIO()
+        response = _mod_request.SuccessfulSmartServerResponse(args, body=body,
+            body_stream=body_stream)
+        responder = protocol.ProtocolThreeResponder(response_io.write)
+        responder.send_response(response)
+        return response_io.getvalue()
+
+    def test__call_doesnt_retry_append(self):
+        response = self.make_response(('appended', '8'))
+        output, vendor, smart_client = self.make_client_with_failing_medium(
+            fail_at_write=False, response=response)
+        smart_request = client._SmartClientRequest(smart_client, 'append',
+            ('foo', ''), body='content\n')
+        self.assertRaises(errors.ConnectionReset, smart_request._call, 3)
+
+    def test__call_retries_get_bytes(self):
+        response = self.make_response(('ok',), 'content\n')
+        output, vendor, smart_client = self.make_client_with_failing_medium(
+            fail_at_write=False, response=response)
+        smart_request = client._SmartClientRequest(smart_client, 'get',
+            ('foo',))
+        response, response_handler = smart_request._call(3)
+        self.assertEqual(('ok',), response)
+        self.assertEqual('content\n', response_handler.read_body_bytes())
+
+    def test__call_noretry_get_bytes(self):
+        debug.debug_flags.add('noretry')
+        response = self.make_response(('ok',), 'content\n')
+        output, vendor, smart_client = self.make_client_with_failing_medium(
+            fail_at_write=False, response=response)
+        smart_request = client._SmartClientRequest(smart_client, 'get',
+            ('foo',))
+        self.assertRaises(errors.ConnectionReset, smart_request._call, 3)
+
     def test__send_no_retry_pipes(self):
         client_read, server_write = create_file_pipes()
         server_read, client_write = create_file_pipes()



More information about the bazaar-commits mailing list