Rev 4886: Merge 2.1-client-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:09:01 UTC 2012


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

------------------------------------------------------------
revno: 4886 [merge]
revision-id: john at arbash-meinel.com-20120912070841-390l0nw5atmd3xlj
parent: john at arbash-meinel.com-20120912070732-8xh7kllfxa6qr3ax
parent: john at arbash-meinel.com-20120911122646-j2x1rxlbgnrkyoox
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: 2.1-all-reconnect-819604
timestamp: Wed 2012-09-12 11:08:41 +0400
message:
  Merge 2.1-client-reconnect-819604
modified:
  bzrlib/osutils.py              osutils.py-20050309040759-eeaff12fbf77ac86
  bzrlib/tests/test_osutils.py   test_osutils.py-20051201224856-e48ee24c12182989
-------------- next part --------------
=== modified file 'bzrlib/osutils.py'
--- a/bzrlib/osutils.py	2010-05-27 04:00:01 +0000
+++ b/bzrlib/osutils.py	2012-09-11 12:26:46 +0000
@@ -40,6 +40,7 @@
     rmtree,
     )
 import signal
+import socket
 import subprocess
 import tempfile
 from tempfile import (
@@ -1929,6 +1930,20 @@
         return socket.gethostname().decode(get_user_encoding())
 
 
+# We must not read/write any more than 64k at a time from/to a socket so we
+# don't risk "no buffer space available" errors on some platforms.  Windows in
+# particular is likely to throw WSAECONNABORTED or WSAENOBUFS if given too much
+# data at once.
+MAX_SOCKET_CHUNK = 64 * 1024
+
+_end_of_stream_errors = [errno.ECONNRESET, errno.EPIPE, errno.EINVAL]
+for _eno in ['WSAECONNRESET', 'WSAECONNABORTED']:
+    _eno = getattr(errno, _eno, None)
+    if _eno is not None:
+        _end_of_stream_errors.append(_eno)
+del _eno
+
+
 def recv_all(socket, bytes):
     """Receive an exact number of bytes.
 
@@ -1948,21 +1963,37 @@
     return b
 
 
-def send_all(socket, bytes, report_activity=None):
+def send_all(sock, bytes, report_activity=None):
     """Send all bytes on a socket.
 
-    Regular socket.sendall() can give socket error 10053 on Windows.  This
-    implementation sends no more than 64k at a time, which avoids this problem.
+    Breaks large blocks in smaller chunks to avoid buffering limitations on
+    some platforms, and catches EINTR which may be thrown if the send is
+    interrupted by a signal.
+
+    This is preferred to socket.sendall(), because it avoids portability bugs
+    and provides activity reporting.
 
     :param report_activity: Call this as bytes are read, see
         Transport._report_activity
     """
-    chunk_size = 2**16
-    for pos in xrange(0, len(bytes), chunk_size):
-        block = bytes[pos:pos+chunk_size]
-        if report_activity is not None:
-            report_activity(len(block), 'write')
-        until_no_eintr(socket.sendall, block)
+    sent_total = 0
+    byte_count = len(bytes)
+    while sent_total < byte_count:
+        try:
+            sent = sock.send(buffer(bytes, sent_total, MAX_SOCKET_CHUNK))
+        except (socket.error, IOError), e:
+            if e.args[0] in _end_of_stream_errors:
+                raise errors.ConnectionReset(
+                    "Error trying to write to socket", e)
+            if e.args[0] != errno.EINTR:
+                raise
+        else:
+            if sent == 0:
+                raise errors.ConnectionReset('Sending to %s returned 0 bytes'
+                                             % (sock,))
+            sent_total += sent
+            if report_activity is not None:
+                report_activity(sent, 'write')
 
 
 def dereference_path(path):

=== modified file 'bzrlib/tests/test_osutils.py'
--- a/bzrlib/tests/test_osutils.py	2010-11-30 20:42:42 +0000
+++ b/bzrlib/tests/test_osutils.py	2012-09-11 12:26:46 +0000
@@ -801,6 +801,45 @@
         self.assertEqual(None, osutils.safe_file_id(None))
 
 
+class TestSendAll(tests.TestCase):
+
+    def test_send_with_disconnected_socket(self):
+        class DisconnectedSocket(object):
+            def __init__(self, err):
+                self.err = err
+            def send(self, content):
+                raise self.err
+            def close(self):
+                pass
+        # All of these should be treated as ConnectionReset
+        errs = []
+        for err_cls in (IOError, socket.error):
+            for errnum in osutils._end_of_stream_errors:
+                errs.append(err_cls(errnum))
+        for err in errs:
+            sock = DisconnectedSocket(err)
+            self.assertRaises(errors.ConnectionReset,
+                osutils.send_all, sock, 'some more content')
+
+    def test_send_with_no_progress(self):
+        # See https://bugs.launchpad.net/bzr/+bug/1047309
+        # It seems that paramiko can get into a state where it doesn't error,
+        # but it returns 0 bytes sent for requests over and over again.
+        class NoSendingSocket(object):
+            def __init__(self):
+                self.call_count = 0
+            def send(self, bytes):
+                self.call_count += 1
+                if self.call_count > 100:
+                    # Prevent the test suite from hanging
+                    raise RuntimeError('too many calls')
+                return 0
+        sock = NoSendingSocket()
+        self.assertRaises(errors.ConnectionReset,
+                          osutils.send_all, sock, 'content')
+        self.assertEqual(1, sock.call_count)
+
+
 class TestWin32Funcs(tests.TestCase):
     """Test that _win32 versions of os utilities return appropriate paths."""
 



More information about the bazaar-commits mailing list