Rev 6144: It turns out that if we don't explicitly close the socket, it hangs around somewhere. in http://bazaar.launchpad.net/~jameinel/bzr/drop-idle-connections-824797
John Arbash Meinel
john at arbash-meinel.com
Wed Sep 14 12:58:26 UTC 2011
At http://bazaar.launchpad.net/~jameinel/bzr/drop-idle-connections-824797
------------------------------------------------------------
revno: 6144
revision-id: john at arbash-meinel.com-20110914125817-h9csfegz6bvz93yj
parent: john at arbash-meinel.com-20110914124017-f9wqua8et07udz61
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: drop-idle-connections-824797
timestamp: Wed 2011-09-14 14:58:17 +0200
message:
It turns out that if we don't explicitly close the socket, it hangs around somewhere.
Which means that the client doesn't *know* that it has been disconnected.
-------------- next part --------------
=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py 2011-09-07 10:17:23 +0000
+++ b/bzrlib/errors.py 2011-09-14 12:58:17 +0000
@@ -1710,6 +1710,11 @@
_fmt = "Connection closed: %(msg)s %(orig_error)s"
+class ConnectionTimeout(ConnectionError):
+
+ _fmt = "Connection Timeout: %(msg)s %(orig_error)s"
+
+
class InvalidRange(TransportError):
_fmt = "Invalid range access in %(path)s at %(offset)s: %(msg)s"
=== modified file 'bzrlib/smart/medium.py'
--- a/bzrlib/smart/medium.py 2011-09-14 12:40:17 +0000
+++ b/bzrlib/smart/medium.py 2011-09-14 12:58:17 +0000
@@ -229,10 +229,32 @@
while not self.finished:
server_protocol = self._build_protocol()
self._serve_one_request(server_protocol)
+ except errors.ConnectionTimeout, e:
+ trace.note('%s' % (e,))
+ self._close()
+ raise
except Exception, e:
stderr.write("%s terminating on exception %s\n" % (self, e))
raise
+ def _close(self):
+ """Close the current connection. We stopped due to a timeout/etc."""
+ # The default implementation is a no-op, because that is all we used to
+ # do when disconnecting from a client. I suppose we never had the
+ # *server* initiate a disconnect, before
+
+ def _wait_for_bytes_with_timeout(self, timeout_seconds):
+ """Wait for more bytes to be read, but timeout if none available.
+
+ This allows us to detect idle connections, and stop trying to read from
+ them, without setting the socket itself to non-blocking. This also
+ allows us to specify when we watch for idle timeouts.
+
+ :return: Did we timeout? (True if we timed out, False if there is data
+ to be read)
+ """
+ raise NotImplementedError(self._wait_for_bytes_with_timeout)
+
def _build_protocol(self):
"""Identifies the version of the incoming request, and returns an
a protocol object that can interpret it.
@@ -248,8 +270,9 @@
# connecting for us to give a more useful message. :(
# (eg, who is on the other side that we are disconnecting
# from)
- raise errors.BzrError('Timeout after %.1f seconds, disconnecting'
- % (self._stream_medium_timeout))
+ raise errors.ConnectionTimeout(
+ 'disconnecting client after %.1f seconds'
+ % (self._stream_medium_timeout))
bytes = self._get_line()
protocol_factory, unused_bytes = _get_protocol_factory_for_bytes(bytes)
protocol = protocol_factory(
@@ -342,6 +365,10 @@
self._push_back(protocol.unused_data)
+ def _close(self):
+ """Close the current connection. We stopped due to a timeout/etc."""
+ self.socket.close()
+
def _wait_for_bytes_with_timeout(self, timeout_seconds):
"""Wait for more bytes to be read, but timeout if none available.
@@ -412,6 +439,10 @@
return
protocol.accept_bytes(bytes)
+ def _close(self):
+ self._in.close()
+ self._out.close()
+
def _wait_for_bytes_with_timeout(self, timeout_seconds):
"""Wait for more bytes to be read, but timeout if none available.
=== modified file 'bzrlib/tests/test_smart_transport.py'
--- a/bzrlib/tests/test_smart_transport.py 2011-09-14 12:40:17 +0000
+++ b/bzrlib/tests/test_smart_transport.py 2011-09-14 12:58:17 +0000
@@ -972,6 +972,16 @@
data = server.read_bytes(1)
self.assertEqual('', data)
+ def test_socket_serve_timeout_closes_socket(self):
+ server_sock, client_sock = self.portable_socket_pair()
+ server = medium.SmartServerSocketStreamMedium(
+ server_sock, None)
+ # This should timeout quickly, and then close the connection so that
+ # client_sock recv doesn't block.
+ server._stream_medium_timeout = 0.1
+ self.assertRaises(errors.ConnectionTimeout, server.serve)
+ self.assertEqual('', client_sock.recv(1))
+
def test_pipe_wait_for_bytes_with_timeout_with_data(self):
# We intentionally use a real pipe here, so that we can 'select' on it.
# You can't select() on a StringIO
More information about the bazaar-commits
mailing list