diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py
index 8d7a2dc1f..f1159e830 100644
--- a/IPython/html/base/zmqhandlers.py
+++ b/IPython/html/base/zmqhandlers.py
@@ -94,11 +94,23 @@ if os.environ.get('IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS', False):
class ZMQStreamHandler(WebSocketHandler):
+ if tornado.version_info < (4,1):
+ """Backport send_error from tornado 4.1 to 4.0"""
+ def send_error(self, *args, **kwargs):
+ if self.stream is None:
+ super(WebSocketHandler, self).send_error(*args, **kwargs)
+ else:
+ # If we get an uncaught exception during the handshake,
+ # we have no choice but to abruptly close the connection.
+ # TODO: for uncaught exceptions after the handshake,
+ # we can close the connection more gracefully.
+ self.stream.close()
+
+
def check_origin(self, origin):
"""Check Origin == Host or Access-Control-Allow-Origin.
Tornado >= 4 calls this method automatically, raising 403 if it returns False.
- We call it explicitly in `open` on Tornado < 4.
"""
if self.allow_origin == '*':
return True
@@ -160,7 +172,10 @@ class ZMQStreamHandler(WebSocketHandler):
def _on_zmq_reply(self, stream, msg_list):
# Sometimes this gets triggered when the on_close method is scheduled in the
# eventloop but hasn't been called.
- if stream.closed(): return
+ if self.stream.closed() or stream.closed():
+ self.log.warn("zmq message arrived on closed channel")
+ self.close()
+ return
channel = getattr(stream, 'channel', None)
try:
msg = self._reserialize_reply(msg_list, channel=channel)