diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py
index 4f9917d..da253cc 100644
--- a/IPython/html/base/zmqhandlers.py
+++ b/IPython/html/base/zmqhandlers.py
@@ -13,9 +13,7 @@ except ImportError:
from urlparse import urlparse # Py 2
import tornado
-from tornado import ioloop
-from tornado import web
-from tornado import websocket
+from tornado import gen, ioloop, web, websocket
from IPython.kernel.zmq.session import Session
from IPython.utils.jsonutil import date_default, extract_dates
@@ -197,7 +195,12 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
"""
pass
- def get(self, *args, **kwargs):
+ def pre_get(self):
+ """Run before finishing the GET request
+
+ Extend this method to add logic that should fire before
+ the websocket finishes completing.
+ """
# Check to see that origin matches host directly, including ports
# Tornado 4 already does CORS checking
if tornado.version_info[0] < 4:
@@ -213,15 +216,22 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
self.session.session = cast_unicode(self.get_argument('session_id'))
else:
self.log.warn("No session ID specified")
+
+ @gen.coroutine
+ def get(self, *args, **kwargs):
+ # pre_get can be a coroutine in subclasses
+ yield gen.maybe_future(self.pre_get())
# FIXME: only do super get on tornado ≥ 4
# tornado 3 has no get, will raise 405
if tornado.version_info >= (4,):
- return super(AuthenticatedZMQStreamHandler, self).get(*args, **kwargs)
+ super(AuthenticatedZMQStreamHandler, self).get(*args, **kwargs)
def initialize(self):
+ self.log.debug("Initializing websocket connection %s", self.request.path)
self.session = Session(config=self.config)
def open(self, *args, **kwargs):
+ self.log.debug("Opening websocket %s", self.request.path)
if tornado.version_info < (4,):
try:
self.get(*self.open_args, **self.open_kwargs)
diff --git a/IPython/html/services/kernels/handlers.py b/IPython/html/services/kernels/handlers.py
index fd2cda3..0590eeb 100644
--- a/IPython/html/services/kernels/handlers.py
+++ b/IPython/html/services/kernels/handlers.py
@@ -7,6 +7,7 @@ import json
import logging
from tornado import gen, web
from tornado.concurrent import Future
+from tornado.ioloop import IOLoop
from IPython.utils.jsonutil import date_default
from IPython.utils.py3compat import cast_unicode
@@ -85,6 +86,10 @@ class KernelActionHandler(IPythonHandler):
class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
+ @property
+ def kernel_info_timeout(self):
+ return self.settings.get('kernel_info_timeout', 10)
+
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized'))
@@ -150,7 +155,8 @@ class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
if protocol_version != kernel_protocol_version:
self.session.adapt_version = int(protocol_version.split('.')[0])
self.log.info("Kernel %s speaks protocol %s", self.kernel_id, protocol_version)
- self._kernel_info_future.set_result(info)
+ if not self._kernel_info_future.done():
+ self._kernel_info_future.set_result(info)
def initialize(self):
super(ZMQChannelHandler, self).initialize()
@@ -160,10 +166,29 @@ class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
self._kernel_info_future = Future()
@gen.coroutine
+ def pre_get(self):
+ # authenticate first
+ super(ZMQChannelHandler, self).pre_get()
+ # then request kernel info, waiting up to a certain time before giving up.
+ # We don't want to wait forever, because browsers don't take it well when
+ # servers never respond to websocket connection requests.
+ future = self.request_kernel_info()
+
+ def give_up():
+ """Don't wait forever for the kernel to reply"""
+ if future.done():
+ return
+ self.log.warn("Timeout waiting for kernel_info reply from %s", self.kernel_id)
+ future.set_result(None)
+ loop = IOLoop.current()
+ loop.add_timeout(loop.time() + self.kernel_info_timeout, give_up)
+ # actually wait for it
+ yield future
+
+ @gen.coroutine
def get(self, kernel_id):
self.kernel_id = cast_unicode(kernel_id, 'ascii')
- yield self.request_kernel_info()
- super(ZMQChannelHandler, self).get(kernel_id)
+ yield super(ZMQChannelHandler, self).get(kernel_id=kernel_id)
def open(self, kernel_id):
super(ZMQChannelHandler, self).open()