Skip to content
73 changes: 3 additions & 70 deletions IPython/frontend/html/notebook/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,9 @@ def _reserialize_reply(self, msg_list):
return jsonapi.dumps(msg, default=date_default)

def _on_zmq_reply(self, msg_list):
# Sometimes this gets triggered when the on_close method is scheduled in the
# eventloop but hasn't been called.
if self.stream.closed(): return
try:
msg = self._reserialize_reply(msg_list)
except Exception:
Expand Down Expand Up @@ -466,10 +469,7 @@ def on_first_message(self, msg):
class IOPubHandler(AuthenticatedZMQStreamHandler):

def initialize(self, *args, **kwargs):
self._kernel_alive = True
self._beating = False
self.iopub_stream = None
self.hb_stream = None

def on_first_message(self, msg):
try:
Expand All @@ -478,12 +478,9 @@ def on_first_message(self, msg):
self.close()
return
km = self.application.kernel_manager
self.time_to_dead = km.time_to_dead
self.first_beat = km.first_beat
kernel_id = self.kernel_id
try:
self.iopub_stream = km.create_iopub_stream(kernel_id)
self.hb_stream = km.create_hb_stream(kernel_id)
except web.HTTPError:
# WebSockets don't response to traditional error codes so we
# close the connection.
Expand All @@ -492,7 +489,6 @@ def on_first_message(self, msg):
self.close()
else:
self.iopub_stream.on_recv(self._on_zmq_reply)
self.start_hb(self.kernel_died)

def on_message(self, msg):
pass
Expand All @@ -501,72 +497,9 @@ def on_close(self):
# This method can be called twice, once by self.kernel_died and once
# from the WebSocket close event. If the WebSocket connection is
# closed before the ZMQ streams are setup, they could be None.
self.stop_hb()
if self.iopub_stream is not None and not self.iopub_stream.closed():
self.iopub_stream.on_recv(None)
self.iopub_stream.close()
if self.hb_stream is not None and not self.hb_stream.closed():
self.hb_stream.close()

def start_hb(self, callback):
"""Start the heartbeating and call the callback if the kernel dies."""
if not self._beating:
self._kernel_alive = True

def ping_or_dead():
self.hb_stream.flush()
if self._kernel_alive:
self._kernel_alive = False
self.hb_stream.send(b'ping')
# flush stream to force immediate socket send
self.hb_stream.flush()
else:
try:
callback()
except:
pass
finally:
self.stop_hb()

def beat_received(msg):
self._kernel_alive = True

self.hb_stream.on_recv(beat_received)
loop = ioloop.IOLoop.instance()
self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000, loop)
loop.add_timeout(time.time()+self.first_beat, self._really_start_hb)
self._beating= True

def _really_start_hb(self):
"""callback for delayed heartbeat start

Only start the hb loop if we haven't been closed during the wait.
"""
if self._beating and not self.hb_stream.closed():
self._hb_periodic_callback.start()

def stop_hb(self):
"""Stop the heartbeating and cancel all related callbacks."""
if self._beating:
self._beating = False
self._hb_periodic_callback.stop()
if not self.hb_stream.closed():
self.hb_stream.on_recv(None)

def _delete_kernel_data(self):
"""Remove the kernel data and notebook mapping."""
self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)

def kernel_died(self):
self._delete_kernel_data()
self.application.log.error("Kernel died: %s" % self.kernel_id)
self.write_message(
{'header': {'msg_type': 'status'},
'parent_header': {},
'content': {'execution_state':'dead'}
}
)
self.on_close()


class ShellHandler(AuthenticatedZMQStreamHandler):
Expand Down
107 changes: 107 additions & 0 deletions IPython/frontend/html/notebook/heartbeat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""A ZMQStream based heartbeat.

This code is currently not used.

Authors:

* Brian Granger
"""

#-----------------------------------------------------------------------------
# Copyright (C) 2008-2011 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------

import time

import zmq
from zmq.eventloop import ioloop


from IPython.config.configurable import LoggingConfigurable
from IPython.utils.traitlets import (
Instance, Float, Bool
)

#-----------------------------------------------------------------------------
# Classes
#-----------------------------------------------------------------------------

class Heartbeat(LoggingConfigurable):

context = Instance('zmq.Context')
def _context_default(self):
return zmq.Context.instance()

loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
def _loop_default(self):
return ioloop.IOLoop.instance()

stream = Instance('zmq.eventloop.zmqstream.ZMQStream')

_beating = Bool(False)
_kernel_alive = Bool(True)

time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")
first_beat = Float(5.0, config=True, help="Delay (in seconds) before sending first heartbeat.")

def start(self, callback):
"""Start the heartbeating and call the callback if the kernel dies."""
if not self._beating:
self._kernel_alive = True

def ping_or_dead():
self.stream.flush()
if self._kernel_alive:
self._kernel_alive = False
self.stream.send(b'ping')
# flush stream to force immediate socket send
self.stream.flush()
else:
try:
callback()
except:
pass
finally:
self.stop()

def beat_received(msg):
self._kernel_alive = True

self.stream.on_recv(beat_received)
self._hb_periodic_callback = ioloop.PeriodicCallback(
ping_or_dead, self.time_to_dead*1000, self.loop
)
self.loop.add_timeout(time.time()+self.first_beat, self._really_start_hb)
self._beating= True

def _really_start_hb(self):
"""callback for delayed heartbeat start

Only start the hb loop if we haven't been closed during the wait.
"""
if self._beating and not self.stream.closed():
self._hb_periodic_callback.start()

def stop(self):
"""Stop the heartbeating and cancel all related callbacks."""
if self._beating:
self._beating = False
self._hb_periodic_callback.stop()
if not self.stream.closed():
self.stream.on_recv(None)

def pause(self):
"""Pause the heartbeat."""
pass

def unpause(self):
"""Unpase the heartbeat."""
pass

6 changes: 2 additions & 4 deletions IPython/frontend/html/notebook/kernelmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@

from IPython.kernel.multikernelmanager import MultiKernelManager
from IPython.utils.traitlets import (
Dict, List, Unicode, Float, Integer,
Dict, List, Unicode, Integer,
)

#-----------------------------------------------------------------------------
# Classes
#-----------------------------------------------------------------------------
Expand All @@ -32,9 +33,6 @@ class MappingKernelManager(MultiKernelManager):

kernel_argv = List(Unicode)

time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")
first_beat = Float(5.0, config=True, help="Delay (in seconds) before sending first heartbeat.")

max_msg_size = Integer(65536, config=True, help="""
The max raw message size accepted from the browser
over a WebSocket connection.
Expand Down
4 changes: 2 additions & 2 deletions IPython/frontend/terminal/console/interactiveshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def run_cell(self, cell, store_history=True):
self.km.shell_channel.get_msg()
# shell_channel.execute takes 'hidden', which is the inverse of store_hist
msg_id = self.km.shell_channel.execute(cell, not store_history)
while not self.km.shell_channel.msg_ready() and self.km.is_alive:
while not self.km.shell_channel.msg_ready() and self.km.is_alive():
try:
self.handle_stdin_request(timeout=0.05)
except Empty:
Expand Down Expand Up @@ -389,7 +389,7 @@ def interact(self, display_banner=None):
# ask_exit callback.

while not self.exit_now:
if not self.km.is_alive:
if not self.km.is_alive():
# kernel died, prompt for action or exit
action = "restart" if self.km.has_kernel else "wait for restart"
ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
Expand Down
2 changes: 1 addition & 1 deletion IPython/kernel/blockingkernelmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def call_handlers(self, since_last_heartbeat):


class BlockingKernelManager(KernelManager):

# The classes to use for the various channels.
shell_channel_class = Type(BlockingShellChannel)
iopub_channel_class = Type(BlockingIOPubChannel)
Expand Down
1 change: 0 additions & 1 deletion IPython/kernel/inprocess/kernelmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,6 @@ def interrupt_kernel(self):
def signal_kernel(self, signum):
raise NotImplementedError("Cannot signal in-process kernel.")

@property
def is_alive(self):
return True

Expand Down
50 changes: 50 additions & 0 deletions IPython/kernel/ioloopkernelmanager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""A kernel manager with ioloop based logic."""

#-----------------------------------------------------------------------------
# Copyright (C) 2013 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------

from __future__ import absolute_import

import zmq
from zmq.eventloop import ioloop

from IPython.utils.traitlets import (
Instance
)

from .blockingkernelmanager import BlockingKernelManager
from .ioloopkernelrestarter import IOLoopKernelRestarter

#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------

class IOLoopKernelManager(BlockingKernelManager):

loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
def _loop_default(self):
return ioloop.IOLoop.instance()

_restarter = Instance('IPython.kernel.ioloopkernelrestarter.IOLoopKernelRestarter')

def start_restarter(self):
if self.autorestart and self.has_kernel:
if self._restarter is None:
self._restarter = IOLoopKernelRestarter(
kernel_manager=self, loop=self.loop,
config=self.config, log=self.log
)
self._restarter.start()

def stop_restarter(self):
if self.autorestart:
if self._restarter is not None:
self._restarter.stop()
Loading