IOStreams now save the exception object that caused them to close.

Closes #494.
This commit is contained in:
Ben Darnell 2012-05-27 15:04:03 -07:00
parent e5d5d3241f
commit 330941924b
2 changed files with 21 additions and 0 deletions

View File

@ -21,6 +21,7 @@ from __future__ import absolute_import, division, with_statement
import collections
import errno
import logging
import os
import socket
import sys
import re
@ -48,6 +49,9 @@ class IOStream(object):
and may either be connected before passing it to the IOStream or
connected with IOStream.connect.
When a stream is closed due to an error, the IOStream's `error`
attribute contains the exception object.
A very simple (and broken) HTTP client using this class::
from tornado import ioloop
@ -84,6 +88,7 @@ class IOStream(object):
self.io_loop = io_loop or ioloop.IOLoop.instance()
self.max_buffer_size = max_buffer_size
self.read_chunk_size = read_chunk_size
self.error = None
self._read_buffer = collections.deque()
self._write_buffer = collections.deque()
self._read_buffer_size = 0
@ -214,6 +219,8 @@ class IOStream(object):
def close(self):
"""Close this stream."""
if self.socket is not None:
if any(sys.exc_info()):
self.error = sys.exc_info()[1]
if self._read_until_close:
callback = self._read_callback
self._read_callback = None
@ -264,6 +271,9 @@ class IOStream(object):
if not self.socket:
return
if events & self.io_loop.ERROR:
errno = self.socket.getsockopt(socket.SOL_SOCKET,
socket.SO_ERROR)
self.error = socket.error(errno, os.strerror(errno))
# We may have queued up a user callback in _handle_read or
# _handle_write, so don't close the IOStream until those
# callbacks have had a chance to run.

View File

@ -5,6 +5,7 @@ from tornado.iostream import IOStream
from tornado.testing import AsyncHTTPTestCase, LogTrapTestCase, get_unused_port
from tornado.util import b
from tornado.web import RequestHandler, Application
import errno
import socket
import time
@ -91,6 +92,16 @@ class TestIOStream(AsyncHTTPTestCase, LogTrapTestCase):
stream.connect(("localhost", port), connect_callback)
self.wait()
self.assertFalse(self.connect_called)
self.assertTrue(isinstance(stream.error, socket.error), stream.error)
self.assertEqual(stream.error.args[0], errno.ECONNREFUSED)
def test_gaierror(self):
# Test that IOStream sets its exc_info on getaddrinfo error
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
stream = IOStream(s, io_loop=self.io_loop)
stream.set_close_callback(self.stop)
stream.connect(('adomainthatdoesntexist.asdf', 54321))
self.assertTrue(isinstance(stream.error, socket.gaierror), stream.error)
def test_connection_closed(self):
# When a server sends a response and then closes the connection,