Merge lp:~spiv/bzr/no_until_no_eintr into lp:bzr

Proposed by Andrew Bennetts
Status: Merged
Approved by: Martin Pool
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~spiv/bzr/no_until_no_eintr
Merge into: lp:bzr
Diff against target: 424 lines (+126/-66)
5 files modified
NEWS (+10/-3)
bzrlib/osutils.py (+77/-18)
bzrlib/smart/medium.py (+30/-44)
bzrlib/smart/protocol.py (+7/-1)
bzrlib/smart/server.py (+2/-0)
To merge this branch: bzr merge lp:~spiv/bzr/no_until_no_eintr
Reviewer Review Type Date Requested Status
Martin Packman (community) Needs Information
Martin Pool Approve
Review via email: mp+21699@code.launchpad.net

Commit message

(andrew) Either correctly handle EINTR or don't handle it at all. (#496813)

Description of the change

This change removes the unsafe uses of until_no_eintr, based on the bug reports and patches from the very patient Martin <gz>. Unlike Martin <gz>'s original patch it doesn't remove the until_no_eintr helper entirely, because it is safe (and useful) to use for reading from a pipe.

(In other changes we've already reduced the number of signal handlers we install that will trigger EINTR, but it can still occur, so, when practical, we may as well handle it correctly rather than fail.)

The most alarming diff hunk is:

- return '\x01'.join(args) + '\n'
+ joined = '\x01'.join(args) + '\n'
+ if type(joined) is unicode:
+ # XXX: We should fix things so this never happens! -AJB, 04032010.
+ mutter('response args contain unicode, should be only bytes: %r',
+ joined)
+ joined = joined.encode('ascii')
+ return joined

This is a latent bug in the HPSS protocol v1 (and maybe v2) code now exposed by the change to use buffer() in send_all. This change explicitly does the convert-to-ASCII that was being done anyway. v3 is unaffected.

The diff applies cleanly to 2.1 (aside from the NEWS entry, of course), and I'll submit a backport for review once this proposal is accepted.

To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote :

nice

review: Approve
Revision history for this message
Martin Pool (mbp) wrote :

If Martin <gz> gets the chance to review it that would be good, otherwise I suggest you merge.

Revision history for this message
Martin Packman (gz) wrote :

Interesting on the encode tuple unicode issue, I didn't see any issues there in testing. Nit, you've got the ISO 8601 basic format wrong, it's big endian.

Having thought about this a bit more, I'm not mad about a bzrlib.smart.medium focused change. The module is already wrappers around wrappers around wrappers, and it's too hard to work out what's going on. Case in point - as far as I can tell, SmartSSHClientMedium still uses get_filelike_channels which uses an unsafe wrapper for sockets pre-2.7 in _ParamikoSSHConnection and non-EINTR-protected pipes in SSHSubprocess. This means we're not covering codepaths like the one bug 341535 highlighted.

I've been waiting for magic bullet inspiration on a clean fix here, but nothing's struck yet. Perhaps a different approach, like unifying on one lowest-common-denominator wrapper for both sockets and pipe fds and using those throughout would be clearer and mean less copy-code to get subtly wrong in certain versions.

review: Needs Information
Revision history for this message
Andrew Bennetts (spiv) wrote :

Martin [gz] wrote:
[...]
> I've been waiting for magic bullet inspiration on a clean fix here, but
> nothing's struck yet. Perhaps a different approach, like unifying on one
> lowest-common-denominator wrapper for both sockets and pipe fds and using
> those throughout would be clearer and mean less copy-code to get subtly wrong
> in certain versions.

I've been wondering about that approach too. It might simplify transport
activity reporting too.

-Andrew.

Revision history for this message
Andrew Bennetts (spiv) wrote :

Martin [gz] wrote:
> Interesting on the encode tuple unicode issue, I didn't see any issues there
> in testing. Nit, you've got the ISO 8601 basic format wrong, it's big endian.

Heh, fixed :)

> Having thought about this a bit more, I'm not mad about a bzrlib.smart.medium
> focused change. The module is already wrappers around wrappers around
> wrappers, and it's too hard to work out what's going on. Case in point - as
> far as I can tell, SmartSSHClientMedium still uses get_filelike_channels which
> uses an unsafe wrapper for sockets pre-2.7 in _ParamikoSSHConnection and
> non-EINTR-protected pipes in SSHSubprocess. This means we're not covering
> codepaths like the one bug 341535 highlighted.

That's true. However I've just sent this patch to PQM anyway because I think
it's still a clear improvement. Here's my reasoning:

  - I think regressing 341535 is better than risking silent corruption of HPSS data
    (and certainly better than a confusing error caused by corrupted HPSS data),
  - Our SIGWINCH handler uses siginterrupt to avoid triggering EINTR, and
  - bzrlib's only other signal handler is SIGQUIT, which is a developer tool not
    a production feature.

I'll reopen 341535, of course.

Thanks for another thoughtful review!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2010-03-25 12:33:15 +0000
+++ NEWS 2010-03-26 04:50:44 +0000
@@ -86,6 +86,9 @@
86* Added docstring for ``Tree.iter_changes``86* Added docstring for ``Tree.iter_changes``
87 (John Arbash Meinel, #304182)87 (John Arbash Meinel, #304182)
8888
89* Allow additional arguments to
90 ``RemoteRepository.add_inventory_by_delta()``. (Jelmer Vernooij, #532631)
91
89* Allow exporting a single file using ``bzr export``.92* Allow exporting a single file using ``bzr export``.
90 (Michal Junák, #511987)93 (Michal Junák, #511987)
9194
@@ -144,6 +147,13 @@
144* Fix stub sftp test server to call os.getcwdu().147* Fix stub sftp test server to call os.getcwdu().
145 (Vincent Ladeuil, #526211, #526353)148 (Vincent Ladeuil, #526211, #526353)
146149
150* Many IO operations that returned ``EINTR`` were retried even if it
151 wasn't safe to do so via careless use of ``until_no_eintr``. Bazaar now
152 only retries operations that are safe to retry, and in some cases has
153 switched to operations that can be retried (e.g. ``sock.send`` rather than
154 ``sock.sendall``).
155 (Andrew Bennetts, Martin <gzlist@googlemail.com>, #496813)
156
147* Path conflicts now support --take-this and --take-other even when a157* Path conflicts now support --take-this and --take-other even when a
148 deletion is involved.158 deletion is involved.
149 (Vincent Ladeuil, #531967)159 (Vincent Ladeuil, #531967)
@@ -163,9 +173,6 @@
163 the debugger won't kill the session.173 the debugger won't kill the session.
164 (Martin <gzlist@googlemail.com>, #162502)174 (Martin <gzlist@googlemail.com>, #162502)
165175
166* Allow additional arguments to
167 ``RemoteRepository.add_inventory_by_delta()``. (Jelmer Vernooij, #532631)
168
169* Tolerate patches with leading noise in ``bzr-handle-patch``.176* Tolerate patches with leading noise in ``bzr-handle-patch``.
170 (Toshio Kuratomi, Martin Pool, #502076)177 (Toshio Kuratomi, Martin Pool, #502076)
171178
172179
=== modified file 'bzrlib/osutils.py'
--- bzrlib/osutils.py 2010-03-25 17:04:08 +0000
+++ bzrlib/osutils.py 2010-03-26 04:50:44 +0000
@@ -40,6 +40,7 @@
40 rmtree,40 rmtree,
41 )41 )
42import signal42import signal
43import socket
43import subprocess44import subprocess
44import tempfile45import tempfile
45from tempfile import (46from tempfile import (
@@ -50,12 +51,16 @@
50from bzrlib import (51from bzrlib import (
51 cache_utf8,52 cache_utf8,
52 errors,53 errors,
54 trace,
53 win32utils,55 win32utils,
54 trace,
55 )56 )
56
57""")57""")
5858
59from bzrlib.symbol_versioning import (
60 deprecated_function,
61 deprecated_in,
62 )
63
59# sha and md5 modules are deprecated in python2.6 but hashlib is available as64# sha and md5 modules are deprecated in python2.6 but hashlib is available as
60# of 2.565# of 2.5
61if sys.version_info < (2, 5):66if sys.version_info < (2, 5):
@@ -1957,40 +1962,82 @@
1957 return socket.gethostname().decode(get_user_encoding())1962 return socket.gethostname().decode(get_user_encoding())
19581963
19591964
1960def recv_all(socket, bytes):1965# We must not read/write any more than 64k at a time from/to a socket so we
1966# don't risk "no buffer space available" errors on some platforms. Windows in
1967# particular is likely to throw WSAECONNABORTED or WSAENOBUFS if given too much
1968# data at once.
1969MAX_SOCKET_CHUNK = 64 * 1024
1970
1971def read_bytes_from_socket(sock, report_activity=None,
1972 max_read_size=MAX_SOCKET_CHUNK):
1973 """Read up to max_read_size of bytes from sock and notify of progress.
1974
1975 Translates "Connection reset by peer" into file-like EOF (return an
1976 empty string rather than raise an error), and repeats the recv if
1977 interrupted by a signal.
1978 """
1979 while 1:
1980 try:
1981 bytes = sock.recv(max_read_size)
1982 except socket.error, e:
1983 eno = e.args[0]
1984 if eno == getattr(errno, "WSAECONNRESET", errno.ECONNRESET):
1985 # The connection was closed by the other side. Callers expect
1986 # an empty string to signal end-of-stream.
1987 return ""
1988 elif eno == errno.EINTR:
1989 # Retry the interrupted recv.
1990 continue
1991 raise
1992 else:
1993 if report_activity is not None:
1994 report_activity(len(bytes), 'read')
1995 return bytes
1996
1997
1998def recv_all(socket, count):
1961 """Receive an exact number of bytes.1999 """Receive an exact number of bytes.
19622000
1963 Regular Socket.recv() may return less than the requested number of bytes,2001 Regular Socket.recv() may return less than the requested number of bytes,
1964 dependning on what's in the OS buffer. MSG_WAITALL is not available2002 depending on what's in the OS buffer. MSG_WAITALL is not available
1965 on all platforms, but this should work everywhere. This will return2003 on all platforms, but this should work everywhere. This will return
1966 less than the requested amount if the remote end closes.2004 less than the requested amount if the remote end closes.
19672005
1968 This isn't optimized and is intended mostly for use in testing.2006 This isn't optimized and is intended mostly for use in testing.
1969 """2007 """
1970 b = ''2008 b = ''
1971 while len(b) < bytes:2009 while len(b) < count:
1972 new = until_no_eintr(socket.recv, bytes - len(b))2010 new = read_bytes_from_socket(socket, None, count - len(b))
1973 if new == '':2011 if new == '':
1974 break # eof2012 break # eof
1975 b += new2013 b += new
1976 return b2014 return b
19772015
19782016
1979def send_all(socket, bytes, report_activity=None):2017def send_all(sock, bytes, report_activity=None):
1980 """Send all bytes on a socket.2018 """Send all bytes on a socket.
19812019
1982 Regular socket.sendall() can give socket error 10053 on Windows. This2020 Breaks large blocks in smaller chunks to avoid buffering limitations on
1983 implementation sends no more than 64k at a time, which avoids this problem.2021 some platforms, and catches EINTR which may be thrown if the send is
19842022 interrupted by a signal.
2023
2024 This is preferred to socket.sendall(), because it avoids portability bugs
2025 and provides activity reporting.
2026
1985 :param report_activity: Call this as bytes are read, see2027 :param report_activity: Call this as bytes are read, see
1986 Transport._report_activity2028 Transport._report_activity
1987 """2029 """
1988 chunk_size = 2**162030 sent_total = 0
1989 for pos in xrange(0, len(bytes), chunk_size):2031 byte_count = len(bytes)
1990 block = bytes[pos:pos+chunk_size]2032 while sent_total < byte_count:
1991 if report_activity is not None:2033 try:
1992 report_activity(len(block), 'write')2034 sent = sock.send(buffer(bytes, sent_total, MAX_SOCKET_CHUNK))
1993 until_no_eintr(socket.sendall, block)2035 except socket.error, e:
2036 if e.args[0] != errno.EINTR:
2037 raise
2038 else:
2039 sent_total += sent
2040 report_activity(sent, 'write')
19942041
19952042
1996def dereference_path(path):2043def dereference_path(path):
@@ -2067,7 +2114,18 @@
20672114
20682115
2069def until_no_eintr(f, *a, **kw):2116def until_no_eintr(f, *a, **kw):
2070 """Run f(*a, **kw), retrying if an EINTR error occurs."""2117 """Run f(*a, **kw), retrying if an EINTR error occurs.
2118
2119 WARNING: you must be certain that it is safe to retry the call repeatedly
2120 if EINTR does occur. This is typically only true for low-level operations
2121 like os.read. If in any doubt, don't use this.
2122
2123 Keep in mind that this is not a complete solution to EINTR. There is
2124 probably code in the Python standard library and other dependencies that
2125 may encounter EINTR if a signal arrives (and there is signal handler for
2126 that signal). So this function can reduce the impact for IO that bzrlib
2127 directly controls, but it is not a complete solution.
2128 """
2071 # Borrowed from Twisted's twisted.python.util.untilConcludes function.2129 # Borrowed from Twisted's twisted.python.util.untilConcludes function.
2072 while True:2130 while True:
2073 try:2131 try:
@@ -2077,6 +2135,7 @@
2077 continue2135 continue
2078 raise2136 raise
20792137
2138
2080def re_compile_checked(re_string, flags=0, where=""):2139def re_compile_checked(re_string, flags=0, where=""):
2081 """Return a compiled re, or raise a sensible error.2140 """Return a compiled re, or raise a sensible error.
20822141
20832142
=== modified file 'bzrlib/smart/medium.py'
--- bzrlib/smart/medium.py 2010-02-17 17:11:16 +0000
+++ bzrlib/smart/medium.py 2010-03-26 04:50:44 +0000
@@ -24,15 +24,14 @@
24bzrlib/transport/smart/__init__.py.24bzrlib/transport/smart/__init__.py.
25"""25"""
2626
27import errno
28import os27import os
29import socket
30import sys28import sys
31import urllib29import urllib
3230
33from bzrlib.lazy_import import lazy_import31from bzrlib.lazy_import import lazy_import
34lazy_import(globals(), """32lazy_import(globals(), """
35import atexit33import atexit
34import socket
36import thread35import thread
37import weakref36import weakref
3837
@@ -47,14 +46,13 @@
47from bzrlib.smart import client, protocol, request, vfs46from bzrlib.smart import client, protocol, request, vfs
48from bzrlib.transport import ssh47from bzrlib.transport import ssh
49""")48""")
50#usually already imported, and getting IllegalScoperReplacer on it here.
51from bzrlib import osutils49from bzrlib import osutils
5250
53# We must not read any more than 64k at a time so we don't risk "no buffer51# Throughout this module buffer size parameters are either limited to be at
54# space available" errors on some platforms. Windows in particular is likely52# most _MAX_READ_SIZE, or are ignored and _MAX_READ_SIZE is used instead.
55# to give error 10053 or 10055 if we read more than 64k from a socket.53# For this module's purposes, MAX_SOCKET_CHUNK is a reasonable size for reads
56_MAX_READ_SIZE = 64 * 102454# from non-sockets as well.
5755_MAX_READ_SIZE = osutils.MAX_SOCKET_CHUNK
5856
59def _get_protocol_factory_for_bytes(bytes):57def _get_protocol_factory_for_bytes(bytes):
60 """Determine the right protocol factory for 'bytes'.58 """Determine the right protocol factory for 'bytes'.
@@ -276,9 +274,9 @@
276 def _serve_one_request_unguarded(self, protocol):274 def _serve_one_request_unguarded(self, protocol):
277 while protocol.next_read_size():275 while protocol.next_read_size():
278 # We can safely try to read large chunks. If there is less data276 # We can safely try to read large chunks. If there is less data
279 # than _MAX_READ_SIZE ready, the socket wil just return a short277 # than MAX_SOCKET_CHUNK ready, the socket will just return a
280 # read immediately rather than block.278 # short read immediately rather than block.
281 bytes = self.read_bytes(_MAX_READ_SIZE)279 bytes = self.read_bytes(osutils.MAX_SOCKET_CHUNK)
282 if bytes == '':280 if bytes == '':
283 self.finished = True281 self.finished = True
284 return282 return
@@ -287,13 +285,13 @@
287 self._push_back(protocol.unused_data)285 self._push_back(protocol.unused_data)
288286
289 def _read_bytes(self, desired_count):287 def _read_bytes(self, desired_count):
290 return _read_bytes_from_socket(288 return osutils.read_bytes_from_socket(
291 self.socket.recv, desired_count, self._report_activity)289 self.socket, self._report_activity)
292290
293 def terminate_due_to_error(self):291 def terminate_due_to_error(self):
294 # TODO: This should log to a server log file, but no such thing292 # TODO: This should log to a server log file, but no such thing
295 # exists yet. Andrew Bennetts 2006-09-29.293 # exists yet. Andrew Bennetts 2006-09-29.
296 osutils.until_no_eintr(self.socket.close)294 self.socket.close()
297 self.finished = True295 self.finished = True
298296
299 def _write_out(self, bytes):297 def _write_out(self, bytes):
@@ -334,27 +332,27 @@
334 bytes_to_read = protocol.next_read_size()332 bytes_to_read = protocol.next_read_size()
335 if bytes_to_read == 0:333 if bytes_to_read == 0:
336 # Finished serving this request.334 # Finished serving this request.
337 osutils.until_no_eintr(self._out.flush)335 self._out.flush()
338 return336 return
339 bytes = self.read_bytes(bytes_to_read)337 bytes = self.read_bytes(bytes_to_read)
340 if bytes == '':338 if bytes == '':
341 # Connection has been closed.339 # Connection has been closed.
342 self.finished = True340 self.finished = True
343 osutils.until_no_eintr(self._out.flush)341 self._out.flush()
344 return342 return
345 protocol.accept_bytes(bytes)343 protocol.accept_bytes(bytes)
346344
347 def _read_bytes(self, desired_count):345 def _read_bytes(self, desired_count):
348 return osutils.until_no_eintr(self._in.read, desired_count)346 return self._in.read(desired_count)
349347
350 def terminate_due_to_error(self):348 def terminate_due_to_error(self):
351 # TODO: This should log to a server log file, but no such thing349 # TODO: This should log to a server log file, but no such thing
352 # exists yet. Andrew Bennetts 2006-09-29.350 # exists yet. Andrew Bennetts 2006-09-29.
353 osutils.until_no_eintr(self._out.close)351 self._out.close()
354 self.finished = True352 self.finished = True
355353
356 def _write_out(self, bytes):354 def _write_out(self, bytes):
357 osutils.until_no_eintr(self._out.write, bytes)355 self._out.write(bytes)
358356
359357
360class SmartClientMediumRequest(object):358class SmartClientMediumRequest(object):
@@ -711,6 +709,10 @@
711 """A client medium using simple pipes.709 """A client medium using simple pipes.
712710
713 This client does not manage the pipes: it assumes they will always be open.711 This client does not manage the pipes: it assumes they will always be open.
712
713 Note that if readable_pipe.read might raise IOError or OSError with errno
714 of EINTR, it must be safe to retry the read. Plain CPython fileobjects
715 (such as used for sys.stdin) are safe.
714 """716 """
715717
716 def __init__(self, readable_pipe, writeable_pipe, base):718 def __init__(self, readable_pipe, writeable_pipe, base):
@@ -720,12 +722,12 @@
720722
721 def _accept_bytes(self, bytes):723 def _accept_bytes(self, bytes):
722 """See SmartClientStreamMedium.accept_bytes."""724 """See SmartClientStreamMedium.accept_bytes."""
723 osutils.until_no_eintr(self._writeable_pipe.write, bytes)725 self._writeable_pipe.write(bytes)
724 self._report_activity(len(bytes), 'write')726 self._report_activity(len(bytes), 'write')
725727
726 def _flush(self):728 def _flush(self):
727 """See SmartClientStreamMedium._flush()."""729 """See SmartClientStreamMedium._flush()."""
728 osutils.until_no_eintr(self._writeable_pipe.flush)730 self._writeable_pipe.flush()
729731
730 def _read_bytes(self, count):732 def _read_bytes(self, count):
731 """See SmartClientStreamMedium._read_bytes."""733 """See SmartClientStreamMedium._read_bytes."""
@@ -777,15 +779,15 @@
777 def _accept_bytes(self, bytes):779 def _accept_bytes(self, bytes):
778 """See SmartClientStreamMedium.accept_bytes."""780 """See SmartClientStreamMedium.accept_bytes."""
779 self._ensure_connection()781 self._ensure_connection()
780 osutils.until_no_eintr(self._write_to.write, bytes)782 self._write_to.write(bytes)
781 self._report_activity(len(bytes), 'write')783 self._report_activity(len(bytes), 'write')
782784
783 def disconnect(self):785 def disconnect(self):
784 """See SmartClientMedium.disconnect()."""786 """See SmartClientMedium.disconnect()."""
785 if not self._connected:787 if not self._connected:
786 return788 return
787 osutils.until_no_eintr(self._read_from.close)789 self._read_from.close()
788 osutils.until_no_eintr(self._write_to.close)790 self._write_to.close()
789 self._ssh_connection.close()791 self._ssh_connection.close()
790 self._connected = False792 self._connected = False
791793
@@ -814,7 +816,7 @@
814 if not self._connected:816 if not self._connected:
815 raise errors.MediumNotConnected(self)817 raise errors.MediumNotConnected(self)
816 bytes_to_read = min(count, _MAX_READ_SIZE)818 bytes_to_read = min(count, _MAX_READ_SIZE)
817 bytes = osutils.until_no_eintr(self._read_from.read, bytes_to_read)819 bytes = self._read_from.read(bytes_to_read)
818 self._report_activity(len(bytes), 'read')820 self._report_activity(len(bytes), 'read')
819 return bytes821 return bytes
820822
@@ -844,7 +846,7 @@
844 """See SmartClientMedium.disconnect()."""846 """See SmartClientMedium.disconnect()."""
845 if not self._connected:847 if not self._connected:
846 return848 return
847 osutils.until_no_eintr(self._socket.close)849 self._socket.close()
848 self._socket = None850 self._socket = None
849 self._connected = False851 self._connected = False
850852
@@ -898,8 +900,8 @@
898 """See SmartClientMedium.read_bytes."""900 """See SmartClientMedium.read_bytes."""
899 if not self._connected:901 if not self._connected:
900 raise errors.MediumNotConnected(self)902 raise errors.MediumNotConnected(self)
901 return _read_bytes_from_socket(903 return osutils.read_bytes_from_socket(
902 self._socket.recv, count, self._report_activity)904 self._socket, self._report_activity)
903905
904906
905class SmartClientStreamMediumRequest(SmartClientMediumRequest):907class SmartClientStreamMediumRequest(SmartClientMediumRequest):
@@ -942,19 +944,3 @@
942 self._medium._flush()944 self._medium._flush()
943945
944946
945def _read_bytes_from_socket(sock, desired_count, report_activity):
946 # We ignore the desired_count because on sockets it's more efficient to
947 # read large chunks (of _MAX_READ_SIZE bytes) at a time.
948 try:
949 bytes = osutils.until_no_eintr(sock, _MAX_READ_SIZE)
950 except socket.error, e:
951 if len(e.args) and e.args[0] in (errno.ECONNRESET, 10054):
952 # The connection was closed by the other side. Callers expect an
953 # empty string to signal end-of-stream.
954 bytes = ''
955 else:
956 raise
957 else:
958 report_activity(len(bytes), 'read')
959 return bytes
960
961947
=== modified file 'bzrlib/smart/protocol.py'
--- bzrlib/smart/protocol.py 2010-02-17 17:11:16 +0000
+++ bzrlib/smart/protocol.py 2010-03-26 04:50:44 +0000
@@ -62,7 +62,13 @@
6262
63def _encode_tuple(args):63def _encode_tuple(args):
64 """Encode the tuple args to a bytestream."""64 """Encode the tuple args to a bytestream."""
65 return '\x01'.join(args) + '\n'65 joined = '\x01'.join(args) + '\n'
66 if type(joined) is unicode:
67 # XXX: We should fix things so this never happens! -AJB, 20100304
68 mutter('response args contain unicode, should be only bytes: %r',
69 joined)
70 joined = joined.encode('ascii')
71 return joined
6672
6773
68class Requester(object):74class Requester(object):
6975
=== modified file 'bzrlib/smart/server.py'
--- bzrlib/smart/server.py 2010-02-23 07:43:11 +0000
+++ bzrlib/smart/server.py 2010-03-26 04:50:44 +0000
@@ -138,6 +138,8 @@
138 if e.args[0] != errno.EBADF:138 if e.args[0] != errno.EBADF:
139 trace.warning("listening socket error: %s", e)139 trace.warning("listening socket error: %s", e)
140 else:140 else:
141 if self._should_terminate:
142 break
141 self.serve_conn(conn, thread_name_suffix)143 self.serve_conn(conn, thread_name_suffix)
142 except KeyboardInterrupt:144 except KeyboardInterrupt:
143 # dont log when CTRL-C'd.145 # dont log when CTRL-C'd.