From: A. Jesse Jiryu Davis Date: Tue, 11 Dec 2012 16:45:21 +0000 (-0500) Subject: Avoid setting IOStream.error to wrong exception, close #651 X-Git-Tag: v3.0.0~191^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=24c3d41748728cf4f3ea1509044b4abc7cea666e;p=thirdparty%2Ftornado.git Avoid setting IOStream.error to wrong exception, close #651 --- diff --git a/tornado/iostream.py b/tornado/iostream.py index 40ac4964f..4c2f0d522 100644 --- a/tornado/iostream.py +++ b/tornado/iostream.py @@ -209,10 +209,14 @@ class BaseIOStream(object): """Call the given callback when the stream is closed.""" self._close_callback = stack_context.wrap(callback) - def close(self): - """Close this stream.""" + def close(self, exc_info=False): + """Close this stream. + + If `exc_info` is true, set the `error` attribute to the current + exception from ``sys.exc_info()``. + """ if not self.closed(): - if any(sys.exc_info()): + if exc_info and any(sys.exc_info()): self.error = sys.exc_info()[1] if self._read_until_close: callback = self._read_callback @@ -285,7 +289,7 @@ class BaseIOStream(object): except Exception: gen_log.error("Uncaught exception, closing connection.", exc_info=True) - self.close() + self.close(exc_info=True) raise def _run_callback(self, callback, *args): @@ -300,7 +304,7 @@ class BaseIOStream(object): # (It would eventually get closed when the socket object is # gc'd, but we don't want to rely on gc happening before we # run out of file descriptors) - self.close() + self.close(exc_info=True) # Re-raise the exception so that IOLoop.handle_callback_exception # can see it and log the error raise @@ -348,7 +352,7 @@ class BaseIOStream(object): self._pending_callbacks -= 1 except Exception: gen_log.warning("error on read", exc_info=True) - self.close() + self.close(exc_info=True) return if self._read_from_buffer(): return @@ -397,9 +401,9 @@ class BaseIOStream(object): # Treat ECONNRESET as a connection close rather than # an error to minimize log spam (the exception will # be available on self.error for apps that care). - self.close() + self.close(exc_info=True) return - self.close() + self.close(exc_info=True) raise if chunk is None: return 0 @@ -503,7 +507,7 @@ class BaseIOStream(object): else: gen_log.warning("Write error on %d: %s", self.fileno(), e) - self.close() + self.close(exc_info=True) return if not self._write_buffer and self._write_callback: callback = self._write_callback @@ -664,7 +668,7 @@ class IOStream(BaseIOStream): if e.args[0] not in (errno.EINPROGRESS, errno.EWOULDBLOCK): gen_log.warning("Connect error on fd %d: %s", self.socket.fileno(), e) - self.close() + self.close(exc_info=True) return self._connect_callback = stack_context.wrap(callback) self._add_io_state(self.io_loop.WRITE) @@ -733,7 +737,7 @@ class SSLIOStream(IOStream): return elif err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN): - return self.close() + return self.close(exc_info=True) elif err.args[0] == ssl.SSL_ERROR_SSL: try: peer = self.socket.getpeername() @@ -741,11 +745,11 @@ class SSLIOStream(IOStream): peer = '(not connected)' gen_log.warning("SSL Error on %d %s: %s", self.socket.fileno(), peer, err) - return self.close() + return self.close(exc_info=True) raise except socket.error, err: if err.args[0] in (errno.ECONNABORTED, errno.ECONNRESET): - return self.close() + return self.close(exc_info=True) else: self._ssl_accepting = False if self._ssl_connect_callback is not None: @@ -842,7 +846,7 @@ class PipeIOStream(BaseIOStream): elif e.args[0] == errno.EBADF: # If the writing half of a pipe is closed, select will # report it as readable but reads will fail with EBADF. - self.close() + self.close(exc_info=True) return None else: raise diff --git a/tornado/test/iostream_test.py b/tornado/test/iostream_test.py index 28c1b78b0..706f596ca 100644 --- a/tornado/test/iostream_test.py +++ b/tornado/test/iostream_test.py @@ -2,7 +2,8 @@ from __future__ import absolute_import, division, with_statement from tornado import netutil from tornado.ioloop import IOLoop from tornado.iostream import IOStream, SSLIOStream, PipeIOStream -from tornado.log import gen_log +from tornado.log import gen_log, app_log +from tornado.stack_context import NullContext from tornado.testing import AsyncHTTPTestCase, AsyncHTTPSTestCase, AsyncTestCase, bind_unused_port, ExpectLog from tornado.test.util import unittest, skipIfNonUnix from tornado.util import b @@ -197,6 +198,24 @@ class TestIOStreamMixin(object): stream.connect(('an invalid domain', 54321)) self.assertTrue(isinstance(stream.error, socket.gaierror), stream.error) + def test_read_callback_error(self): + # Test that IOStream sets its exc_info when a read callback throws + server, client = self.make_iostream_pair() + try: + server.set_close_callback(self.stop) + with ExpectLog( + app_log, "(Uncaught exception|Exception in callback)" + ): + # Clear ExceptionStackContext so IOStream catches error + with NullContext(): + server.read_bytes(1, callback=lambda data: 1 / 0) + client.write(b("1")) + self.wait() + self.assertTrue(isinstance(server.error, ZeroDivisionError)) + finally: + server.close() + client.close() + def test_streaming_callback(self): server, client = self.make_iostream_pair() try: