Permalink
Browse files

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

Closes #494.
  • Loading branch information...
1 parent e5d5d32 commit 330941924b78e298773416ca2d0ace08dcc9e000 @bdarnell bdarnell committed May 27, 2012
Showing with 21 additions and 0 deletions.
  1. +10 −0 tornado/iostream.py
  2. +11 −0 tornado/test/iostream_test.py
View
@@ -21,6 +21,7 @@
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 @@ def __init__(self, socket, io_loop=None, max_buffer_size=104857600,
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 @@ def set_close_callback(self, callback):
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 @@ def _handle_events(self, fd, events):
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.
@@ -5,6 +5,7 @@
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 @@ def connect_callback():
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,

0 comments on commit 3309419

Please sign in to comment.