Repository: ipython/ipython

Issue:
Use WebSocket.ping to keep connections alive across proxies
I am accessing a remote IPython Notebook and need to use a proxy (Squid/SOCKS) for internet access.
As long as I am running cells everything works fine, but after one minute of inactivity the proxy seems to close the connection, resulting in the following Firefox console log:

```
The connection to wss://example.org/kernels/$uuid/shell was interrupted while the page 
was loading. @ https://example.org/static/js/kernel.js
```

The websocket connection is reestablished immediately (and interrupted again after one minute), but running cells does not work anymore until I manually restart the kernel.

```
ipython --version
0.13.1.rc2
```

Sending any message via each websocket (shell+iopub) will avoid interruption. This is a quick and _dirty_ client side workaround:

```
from IPython.core.display import HTML
HTML('''<script>
var keepalive_timer;
function keep_connection_alive() {
    IPython.notebook.kernel.execute();
    keepalive_timer = setTimeout('keep_connection_alive()', 55000);
}
keep_connection_alive();
</script>''')
```

WebSocket has ping and pong frames for keep-alive, tornado has a ping() function to send such a frame. Could the ping function be called somewhere in the kernel code to keep alive connections even via proxy?



Discussion:
It really seems like this is a proxy configuration issue, can you change the connection interrupt timer in the proxy to a more sensible value?

Interruption is a server side proxy issue (nginx in this case), I set the timeout to 10 minutes (1 minute is default). The non-responding kernel after interruption seems to be related to the client side proxy.

The Notebook could still send ping frames every few minutes to keep the connection alive. Tornado does not send them automatically, I think the function which could be used is `WebSocketHandler.ping(data)`.

This is the second situation we have seen this in. We should probably address this on our side.

I've been annoyed by something like this for a while (running cells mysteriously stops working, usually if I've been doing something else for a little while, then coming back to the notebook). I just tried to replicate it and couldn't. When it does happen, the "busy" circle stays open (as not busy), and an asterisk appears next to the cell that is meant to run, but it doesn't end up getting run.
I'm happy to do some troubleshooting if anyone has suggestions.

I'm using firefox 30 with ubuntu 14.04, to connect to a home server running ipython notebook, through my university's network (which has a proxy).


Resolving patch:
diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py
--- a/IPython/html/base/zmqhandlers.py
+++ b/IPython/html/base/zmqhandlers.py
@@ -17,6 +17,7 @@
 import logging
 
 import tornado
+from tornado import ioloop
 from tornado import web
 from tornado import websocket
 
@@ -103,8 +104,12 @@ def allow_draft76(self):
         """
         return True
 
+# ping interval for keeping websockets alive (30 seconds)
+WS_PING_INTERVAL = 30000
 
 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
+    ping_callback = None
+
     def set_default_headers(self):
         """Undo the set_default_headers in IPythonHandler
         
@@ -124,6 +129,16 @@ def open(self, kernel_id):
         self.session = Session(config=self.config)
         self.save_on_message = self.on_message
         self.on_message = self.on_first_message
+        self.ping_callback = ioloop.PeriodicCallback(self.send_ping, WS_PING_INTERVAL)
+        self.ping_callback.start()
+
+    def send_ping(self):
+        """send a ping to keep the websocket alive"""
+        if self.stream.closed() and self.ping_callback is not None:
+            self.ping_callback.stop()
+            return
+
+        self.ping(b'')
 
     def _inject_cookie_message(self, msg):
         """Inject the first message, which is the document cookie,


Test patch: