##// END OF EJS Templates
Merge pull request #3011 from minrk/kernelclient...
Brian E. Granger -
r10354:ce1ad539 merge
parent child Browse files
Show More
@@ -0,0 +1,52 b''
1 """ Defines a KernelClient that provides signals and slots.
2 """
3
4 from IPython.external.qt import QtCore
5
6 # Local imports
7 from IPython.utils.traitlets import Bool, Instance
8
9 from IPython.kernel import KernelManager
10 from IPython.kernel.restarter import KernelRestarter
11
12 from .kernel_mixins import QtKernelManagerMixin, QtKernelRestarterMixin
13
14
15 class QtKernelRestarter(KernelRestarter, QtKernelRestarterMixin):
16
17 def start(self):
18 if self._timer is None:
19 self._timer = QtCore.QTimer()
20 self._timer.timeout.connect(self.poll)
21 self._timer.start(self.time_to_dead * 1000)
22
23 def stop(self):
24 self._timer.stop()
25
26 def poll(self):
27 super(QtKernelRestarter, self).poll()
28
29
30 class QtKernelManager(KernelManager, QtKernelManagerMixin):
31 """A KernelManager with Qt signals for restart"""
32
33 autorestart = Bool(True, config=True)
34
35 def start_restarter(self):
36 if self.autorestart and self.has_kernel:
37 if self._restarter is None:
38 self._restarter = QtKernelRestarter(
39 kernel_manager=self,
40 config=self.config,
41 log=self.log,
42 )
43 self._restarter.add_callback(self._handle_kernel_restarted)
44 self._restarter.start()
45
46 def stop_restarter(self):
47 if self.autorestart:
48 if self._restarter is not None:
49 self._restarter.stop()
50
51 def _handle_kernel_restarted(self):
52 self.kernel_restarted.emit()
@@ -0,0 +1,1 b''
1 from .client import BlockingKernelClient No newline at end of file
@@ -0,0 +1,33 b''
1 """Implements a fully blocking kernel client.
2
3 Useful for test suites and blocking terminal interfaces.
4 """
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2013 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING.txt, distributed as part of this software.
10 #-----------------------------------------------------------------------------
11
12 #-----------------------------------------------------------------------------
13 # Imports
14 #-----------------------------------------------------------------------------
15
16 from IPython.utils.traitlets import Type
17 from IPython.kernel.client import KernelClient
18 from .channels import (
19 BlockingIOPubChannel, BlockingHBChannel,
20 BlockingShellChannel, BlockingStdInChannel
21 )
22
23 #-----------------------------------------------------------------------------
24 # Blocking kernel manager
25 #-----------------------------------------------------------------------------
26
27 class BlockingKernelClient(KernelClient):
28
29 # The classes to use for the various channels
30 shell_channel_class = Type(BlockingShellChannel)
31 iopub_channel_class = Type(BlockingIOPubChannel)
32 stdin_channel_class = Type(BlockingStdInChannel)
33 hb_channel_class = Type(BlockingHBChannel)
@@ -0,0 +1,126 b''
1 """Abstract base classes for kernel client channels"""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 # Standard library imports
15 import abc
16
17 #-----------------------------------------------------------------------------
18 # Channels
19 #-----------------------------------------------------------------------------
20
21
22 class ChannelABC(object):
23 """A base class for all channel ABCs."""
24
25 __metaclass__ = abc.ABCMeta
26
27 @abc.abstractmethod
28 def start(self):
29 pass
30
31 @abc.abstractmethod
32 def stop(self):
33 pass
34
35 @abc.abstractmethod
36 def is_alive(self):
37 pass
38
39
40 class ShellChannelABC(ChannelABC):
41 """ShellChannel ABC.
42
43 The docstrings for this class can be found in the base implementation:
44
45 `IPython.kernel.channels.ShellChannel`
46 """
47
48 @abc.abstractproperty
49 def allow_stdin(self):
50 pass
51
52 @abc.abstractmethod
53 def execute(self, code, silent=False, store_history=True,
54 user_variables=None, user_expressions=None, allow_stdin=None):
55 pass
56
57 @abc.abstractmethod
58 def complete(self, text, line, cursor_pos, block=None):
59 pass
60
61 @abc.abstractmethod
62 def object_info(self, oname, detail_level=0):
63 pass
64
65 @abc.abstractmethod
66 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
67 pass
68
69 @abc.abstractmethod
70 def kernel_info(self):
71 pass
72
73 @abc.abstractmethod
74 def shutdown(self, restart=False):
75 pass
76
77
78 class IOPubChannelABC(ChannelABC):
79 """IOPubChannel ABC.
80
81 The docstrings for this class can be found in the base implementation:
82
83 `IPython.kernel.channels.IOPubChannel`
84 """
85
86 @abc.abstractmethod
87 def flush(self, timeout=1.0):
88 pass
89
90
91 class StdInChannelABC(ChannelABC):
92 """StdInChannel ABC.
93
94 The docstrings for this class can be found in the base implementation:
95
96 `IPython.kernel.channels.StdInChannel`
97 """
98
99 @abc.abstractmethod
100 def input(self, string):
101 pass
102
103
104 class HBChannelABC(ChannelABC):
105 """HBChannel ABC.
106
107 The docstrings for this class can be found in the base implementation:
108
109 `IPython.kernel.channels.HBChannel`
110 """
111
112 @abc.abstractproperty
113 def time_to_dead(self):
114 pass
115
116 @abc.abstractmethod
117 def pause(self):
118 pass
119
120 @abc.abstractmethod
121 def unpause(self):
122 pass
123
124 @abc.abstractmethod
125 def is_beating(self):
126 pass
@@ -0,0 +1,198 b''
1 """Base class to manage the interaction with a running kernel
2 """
3
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
15 from __future__ import absolute_import
16
17 import zmq
18
19 # Local imports
20 from IPython.config.configurable import LoggingConfigurable
21 from IPython.utils.traitlets import (
22 Any, Instance, Type,
23 )
24
25 from .zmq.session import Session
26 from .channels import (
27 ShellChannel, IOPubChannel,
28 HBChannel, StdInChannel,
29 )
30 from .clientabc import KernelClientABC
31 from .connect import ConnectionFileMixin
32
33
34 #-----------------------------------------------------------------------------
35 # Main kernel client class
36 #-----------------------------------------------------------------------------
37
38 class KernelClient(LoggingConfigurable, ConnectionFileMixin):
39 """Communicates with a single kernel on any host via zmq channels.
40
41 There are four channels associated with each kernel:
42
43 * shell: for request/reply calls to the kernel.
44 * iopub: for the kernel to publish results to frontends.
45 * hb: for monitoring the kernel's heartbeat.
46 * stdin: for frontends to reply to raw_input calls in the kernel.
47
48 The methods of the channels are exposed as methods of the client itself
49 (KernelClient.execute, complete, history, etc.).
50 See the channels themselves for documentation of these methods.
51
52 """
53
54 # The PyZMQ Context to use for communication with the kernel.
55 context = Instance(zmq.Context)
56 def _context_default(self):
57 return zmq.Context.instance()
58
59 # The Session to use for communication with the kernel.
60 session = Instance(Session)
61 def _session_default(self):
62 return Session(config=self.config)
63
64 # The classes to use for the various channels
65 shell_channel_class = Type(ShellChannel)
66 iopub_channel_class = Type(IOPubChannel)
67 stdin_channel_class = Type(StdInChannel)
68 hb_channel_class = Type(HBChannel)
69
70 # Protected traits
71 _shell_channel = Any
72 _iopub_channel = Any
73 _stdin_channel = Any
74 _hb_channel = Any
75
76 #--------------------------------------------------------------------------
77 # Channel proxy methods
78 #--------------------------------------------------------------------------
79
80 def _get_msg(channel, *args, **kwargs):
81 return channel.get_msg(*args, **kwargs)
82
83 def get_shell_msg(self, *args, **kwargs):
84 """Get a message from the shell channel"""
85 return self.shell_channel.get_msg(*args, **kwargs)
86
87 def get_iopub_msg(self, *args, **kwargs):
88 """Get a message from the iopub channel"""
89 return self.iopub_channel.get_msg(*args, **kwargs)
90
91 def get_stdin_msg(self, *args, **kwargs):
92 """Get a message from the stdin channel"""
93 return self.stdin_channel.get_msg(*args, **kwargs)
94
95 #--------------------------------------------------------------------------
96 # Channel management methods
97 #--------------------------------------------------------------------------
98
99 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
100 """Starts the channels for this kernel.
101
102 This will create the channels if they do not exist and then start
103 them (their activity runs in a thread). If port numbers of 0 are
104 being used (random ports) then you must first call
105 :method:`start_kernel`. If the channels have been stopped and you
106 call this, :class:`RuntimeError` will be raised.
107 """
108 if shell:
109 self.shell_channel.start()
110 for method in self.shell_channel.proxy_methods:
111 setattr(self, method, getattr(self.shell_channel, method))
112 if iopub:
113 self.iopub_channel.start()
114 for method in self.iopub_channel.proxy_methods:
115 setattr(self, method, getattr(self.iopub_channel, method))
116 if stdin:
117 self.stdin_channel.start()
118 for method in self.stdin_channel.proxy_methods:
119 setattr(self, method, getattr(self.stdin_channel, method))
120 self.shell_channel.allow_stdin = True
121 else:
122 self.shell_channel.allow_stdin = False
123 if hb:
124 self.hb_channel.start()
125
126 def stop_channels(self):
127 """Stops all the running channels for this kernel.
128
129 This stops their event loops and joins their threads.
130 """
131 if self.shell_channel.is_alive():
132 self.shell_channel.stop()
133 if self.iopub_channel.is_alive():
134 self.iopub_channel.stop()
135 if self.stdin_channel.is_alive():
136 self.stdin_channel.stop()
137 if self.hb_channel.is_alive():
138 self.hb_channel.stop()
139
140 @property
141 def channels_running(self):
142 """Are any of the channels created and running?"""
143 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
144 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
145
146 @property
147 def shell_channel(self):
148 """Get the shell channel object for this kernel."""
149 if self._shell_channel is None:
150 self._shell_channel = self.shell_channel_class(
151 self.context, self.session, self._make_url('shell')
152 )
153 return self._shell_channel
154
155 @property
156 def iopub_channel(self):
157 """Get the iopub channel object for this kernel."""
158 if self._iopub_channel is None:
159 self._iopub_channel = self.iopub_channel_class(
160 self.context, self.session, self._make_url('iopub')
161 )
162 return self._iopub_channel
163
164 @property
165 def stdin_channel(self):
166 """Get the stdin channel object for this kernel."""
167 if self._stdin_channel is None:
168 self._stdin_channel = self.stdin_channel_class(
169 self.context, self.session, self._make_url('stdin')
170 )
171 return self._stdin_channel
172
173 @property
174 def hb_channel(self):
175 """Get the hb channel object for this kernel."""
176 if self._hb_channel is None:
177 self._hb_channel = self.hb_channel_class(
178 self.context, self.session, self._make_url('hb')
179 )
180 return self._hb_channel
181
182 def is_alive(self):
183 """Is the kernel process still running?"""
184 if self._hb_channel is not None:
185 # We didn't start the kernel with this KernelManager so we
186 # use the heartbeat.
187 return self._hb_channel.is_beating()
188 else:
189 # no heartbeat and not local, we can't tell if it's running,
190 # so naively return True
191 return True
192
193
194 #-----------------------------------------------------------------------------
195 # ABC Registration
196 #-----------------------------------------------------------------------------
197
198 KernelClientABC.register(KernelClient)
@@ -0,0 +1,81 b''
1 """Abstract base class for kernel clients"""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 # Standard library imports
15 import abc
16
17 #-----------------------------------------------------------------------------
18 # Main kernel client class
19 #-----------------------------------------------------------------------------
20
21 class KernelClientABC(object):
22 """KernelManager ABC.
23
24 The docstrings for this class can be found in the base implementation:
25
26 `IPython.kernel.client.KernelClient`
27 """
28
29 __metaclass__ = abc.ABCMeta
30
31 @abc.abstractproperty
32 def kernel(self):
33 pass
34
35 @abc.abstractproperty
36 def shell_channel_class(self):
37 pass
38
39 @abc.abstractproperty
40 def iopub_channel_class(self):
41 pass
42
43 @abc.abstractproperty
44 def hb_channel_class(self):
45 pass
46
47 @abc.abstractproperty
48 def stdin_channel_class(self):
49 pass
50
51 #--------------------------------------------------------------------------
52 # Channel management methods
53 #--------------------------------------------------------------------------
54
55 @abc.abstractmethod
56 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
57 pass
58
59 @abc.abstractmethod
60 def stop_channels(self):
61 pass
62
63 @abc.abstractproperty
64 def channels_running(self):
65 pass
66
67 @abc.abstractproperty
68 def shell_channel(self):
69 pass
70
71 @abc.abstractproperty
72 def iopub_channel(self):
73 pass
74
75 @abc.abstractproperty
76 def stdin_channel(self):
77 pass
78
79 @abc.abstractproperty
80 def hb_channel(self):
81 pass
@@ -0,0 +1,87 b''
1 """A client for in-process kernels."""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2012 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 # IPython imports
15 from IPython.utils.traitlets import Type, Instance
16 from IPython.kernel.clientabc import KernelClientABC
17 from IPython.kernel.client import KernelClient
18
19 # Local imports
20 from .channels import (
21 InProcessShellChannel,
22 InProcessIOPubChannel,
23 InProcessHBChannel,
24 InProcessStdInChannel,
25
26 )
27
28 #-----------------------------------------------------------------------------
29 # Main kernel Client class
30 #-----------------------------------------------------------------------------
31
32 class InProcessKernelClient(KernelClient):
33 """A client for an in-process kernel.
34
35 This class implements the interface of
36 `IPython.kernel.clientabc.KernelClientABC` and allows
37 (asynchronous) frontends to be used seamlessly with an in-process kernel.
38
39 See `IPython.kernel.client.KernelClient` for docstrings.
40 """
41
42 # The classes to use for the various channels.
43 shell_channel_class = Type(InProcessShellChannel)
44 iopub_channel_class = Type(InProcessIOPubChannel)
45 stdin_channel_class = Type(InProcessStdInChannel)
46 hb_channel_class = Type(InProcessHBChannel)
47
48 kernel = Instance('IPython.kernel.inprocess.ipkernel.Kernel')
49
50 #--------------------------------------------------------------------------
51 # Channel management methods
52 #--------------------------------------------------------------------------
53
54 def start_channels(self, *args, **kwargs):
55 super(InProcessKernelClient, self).start_channels(self)
56 self.kernel.frontends.append(self)
57
58 @property
59 def shell_channel(self):
60 if self._shell_channel is None:
61 self._shell_channel = self.shell_channel_class(self)
62 return self._shell_channel
63
64 @property
65 def iopub_channel(self):
66 if self._iopub_channel is None:
67 self._iopub_channel = self.iopub_channel_class(self)
68 return self._iopub_channel
69
70 @property
71 def stdin_channel(self):
72 if self._stdin_channel is None:
73 self._stdin_channel = self.stdin_channel_class(self)
74 return self._stdin_channel
75
76 @property
77 def hb_channel(self):
78 if self._hb_channel is None:
79 self._hb_channel = self.hb_channel_class(self)
80 return self._hb_channel
81
82
83 #-----------------------------------------------------------------------------
84 # ABC Registration
85 #-----------------------------------------------------------------------------
86
87 KernelClientABC.register(InProcessKernelClient)
@@ -0,0 +1,77 b''
1 """A kernel manager for in-process kernels."""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 from IPython.utils.traitlets import Instance, DottedObjectName
15 from IPython.kernel.managerabc import KernelManagerABC
16 from IPython.kernel.manager import KernelManager
17
18 #-----------------------------------------------------------------------------
19 # Main kernel manager class
20 #-----------------------------------------------------------------------------
21
22 class InProcessKernelManager(KernelManager):
23 """A manager for an in-process kernel.
24
25 This class implements the interface of
26 `IPython.kernel.kernelmanagerabc.KernelManagerABC` and allows
27 (asynchronous) frontends to be used seamlessly with an in-process kernel.
28
29 See `IPython.kernel.kernelmanager.KernelManager` for docstrings.
30 """
31
32 # The kernel process with which the KernelManager is communicating.
33 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel')
34 # the client class for KM.client() shortcut
35 client_class = DottedObjectName('IPython.kernel.inprocess.BlockingInProcessKernelClient')
36
37 #--------------------------------------------------------------------------
38 # Kernel management methods
39 #--------------------------------------------------------------------------
40
41 def start_kernel(self, **kwds):
42 from IPython.kernel.inprocess.ipkernel import InProcessKernel
43 self.kernel = InProcessKernel()
44
45 def shutdown_kernel(self):
46 self._kill_kernel()
47
48 def restart_kernel(self, now=False, **kwds):
49 self.shutdown_kernel()
50 self.start_kernel(**kwds)
51
52 @property
53 def has_kernel(self):
54 return self.kernel is not None
55
56 def _kill_kernel(self):
57 self.kernel = None
58
59 def interrupt_kernel(self):
60 raise NotImplementedError("Cannot interrupt in-process kernel.")
61
62 def signal_kernel(self, signum):
63 raise NotImplementedError("Cannot signal in-process kernel.")
64
65 def is_alive(self):
66 return self.kernel is not None
67
68 def client(self, **kwargs):
69 kwargs['kernel'] = self.kernel
70 return super(InProcessKernelManager, self).client(**kwargs)
71
72
73 #-----------------------------------------------------------------------------
74 # ABC Registration
75 #-----------------------------------------------------------------------------
76
77 KernelManagerABC.register(InProcessKernelManager)
@@ -0,0 +1,2 b''
1 from .manager import IOLoopKernelManager
2 from .restarter import IOLoopKernelRestarter
@@ -0,0 +1,63 b''
1 """A kernel manager with a tornado IOLoop"""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 from __future__ import absolute_import
15
16 import zmq
17 from zmq.eventloop import ioloop
18 from zmq.eventloop.zmqstream import ZMQStream
19
20 from IPython.utils.traitlets import (
21 Instance
22 )
23
24 from IPython.kernel.manager import KernelManager
25 from .restarter import IOLoopKernelRestarter
26
27 #-----------------------------------------------------------------------------
28 # Code
29 #-----------------------------------------------------------------------------
30
31
32 def as_zmqstream(f):
33 def wrapped(self, *args, **kwargs):
34 socket = f(self, *args, **kwargs)
35 return ZMQStream(socket, self.loop)
36 return wrapped
37
38 class IOLoopKernelManager(KernelManager):
39
40 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
41 def _loop_default(self):
42 return ioloop.IOLoop.instance()
43
44 _restarter = Instance('IPython.kernel.ioloop.IOLoopKernelRestarter')
45
46 def start_restarter(self):
47 if self.autorestart and self.has_kernel:
48 if self._restarter is None:
49 self._restarter = IOLoopKernelRestarter(
50 kernel_manager=self, loop=self.loop,
51 config=self.config, log=self.log
52 )
53 self._restarter.start()
54
55 def stop_restarter(self):
56 if self.autorestart:
57 if self._restarter is not None:
58 self._restarter.stop()
59
60 connect_shell = as_zmqstream(KernelManager.connect_shell)
61 connect_iopub = as_zmqstream(KernelManager.connect_iopub)
62 connect_stdin = as_zmqstream(KernelManager.connect_stdin)
63 connect_hb = as_zmqstream(KernelManager.connect_hb)
@@ -0,0 +1,55 b''
1 """A basic in process kernel monitor with autorestarting.
2
3 This watches a kernel's state using KernelManager.is_alive and auto
4 restarts the kernel if it dies.
5 """
6
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2013 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17
18 from __future__ import absolute_import
19
20 import zmq
21 from zmq.eventloop import ioloop
22
23
24 from IPython.kernel.restarter import KernelRestarter
25 from IPython.utils.traitlets import (
26 Instance, Float, List,
27 )
28
29 #-----------------------------------------------------------------------------
30 # Code
31 #-----------------------------------------------------------------------------
32
33 class IOLoopKernelRestarter(KernelRestarter):
34 """Monitor and autorestart a kernel."""
35
36 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
37 def _loop_default(self):
38 return ioloop.IOLoop.instance()
39
40 _pcallback = None
41
42 def start(self):
43 """Start the polling of the kernel."""
44 if self._pcallback is None:
45 self._pcallback = ioloop.PeriodicCallback(
46 self.poll, 1000*self.time_to_dead, self.loop
47 )
48 self._pcallback.start()
49
50 def stop(self):
51 """Stop the kernel polling."""
52 if self._pcallback is not None:
53 self._pcallback.stop()
54 self._pcallback = None
55
@@ -0,0 +1,379 b''
1 """Base class to manage a running kernel
2 """
3
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
15 from __future__ import absolute_import
16
17 # Standard library imports
18 import signal
19 import sys
20 import time
21
22 import zmq
23
24 # Local imports
25 from IPython.config.configurable import LoggingConfigurable
26 from IPython.utils.importstring import import_item
27 from IPython.utils.localinterfaces import LOCAL_IPS
28 from IPython.utils.traitlets import (
29 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
30 )
31 from IPython.kernel import (
32 make_ipkernel_cmd,
33 launch_kernel,
34 )
35 from .connect import ConnectionFileMixin
36 from .zmq.session import Session
37 from .managerabc import (
38 KernelManagerABC
39 )
40
41 #-----------------------------------------------------------------------------
42 # Main kernel manager class
43 #-----------------------------------------------------------------------------
44
45 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
46 """Manages a single kernel in a subprocess on this host.
47
48 This version starts kernels with Popen.
49 """
50
51 # The PyZMQ Context to use for communication with the kernel.
52 context = Instance(zmq.Context)
53 def _context_default(self):
54 return zmq.Context.instance()
55
56 # The Session to use for communication with the kernel.
57 session = Instance(Session)
58 def _session_default(self):
59 return Session(config=self.config)
60
61 # the class to create with our `client` method
62 client_class = DottedObjectName('IPython.kernel.client.KernelClient')
63 client_factory = Type()
64 def _client_class_changed(self, name, old, new):
65 self.client_factory = import_item(str(new))
66
67 # The kernel process with which the KernelManager is communicating.
68 # generally a Popen instance
69 kernel = Any()
70
71 kernel_cmd = List(Unicode, config=True,
72 help="""The Popen Command to launch the kernel.
73 Override this if you have a custom
74 """
75 )
76
77 def _kernel_cmd_changed(self, name, old, new):
78 self.ipython_kernel = False
79
80 ipython_kernel = Bool(True)
81
82 # Protected traits
83 _launch_args = Any()
84 _control_socket = Any()
85
86 _restarter = Any()
87
88 autorestart = Bool(False, config=True,
89 help="""Should we autorestart the kernel if it dies."""
90 )
91
92 def __del__(self):
93 self._close_control_socket()
94 self.cleanup_connection_file()
95
96 #--------------------------------------------------------------------------
97 # Kernel restarter
98 #--------------------------------------------------------------------------
99
100 def start_restarter(self):
101 pass
102
103 def stop_restarter(self):
104 pass
105
106 def add_restart_callback(self, callback, event='restart'):
107 """register a callback to be called when a kernel is restarted"""
108 if self._restarter is None:
109 return
110 self._restarter.add_callback(callback, event)
111
112 def remove_restart_callback(self, callback, event='restart'):
113 """unregister a callback to be called when a kernel is restarted"""
114 if self._restarter is None:
115 return
116 self._restarter.remove_callback(callback, event)
117
118 #--------------------------------------------------------------------------
119 # create a Client connected to our Kernel
120 #--------------------------------------------------------------------------
121
122 def client(self, **kwargs):
123 """Create a client configured to connect to our kernel"""
124 if self.client_factory is None:
125 self.client_factory = import_item(self.client_class)
126
127 kw = {}
128 kw.update(self.get_connection_info())
129 kw.update(dict(
130 connection_file=self.connection_file,
131 session=self.session,
132 config=self.config,
133 ))
134
135 # add kwargs last, for manual overrides
136 kw.update(kwargs)
137 return self.client_factory(**kw)
138
139 #--------------------------------------------------------------------------
140 # Kernel management
141 #--------------------------------------------------------------------------
142
143 def format_kernel_cmd(self, **kw):
144 """format templated args (e.g. {connection_file})"""
145 if self.kernel_cmd:
146 cmd = self.kernel_cmd
147 else:
148 cmd = make_ipkernel_cmd(
149 'from IPython.kernel.zmq.kernelapp import main; main()',
150 **kw
151 )
152 ns = dict(connection_file=self.connection_file)
153 ns.update(self._launch_args)
154 return [ c.format(**ns) for c in cmd ]
155
156 def _launch_kernel(self, kernel_cmd, **kw):
157 """actually launch the kernel
158
159 override in a subclass to launch kernel subprocesses differently
160 """
161 return launch_kernel(kernel_cmd, **kw)
162
163 # Control socket used for polite kernel shutdown
164
165 def _connect_control_socket(self):
166 if self._control_socket is None:
167 self._control_socket = self.connect_control()
168 self._control_socket.linger = 100
169
170 def _close_control_socket(self):
171 if self._control_socket is None:
172 return
173 self._control_socket.close()
174 self._control_socket = None
175
176 def start_kernel(self, **kw):
177 """Starts a kernel on this host in a separate process.
178
179 If random ports (port=0) are being used, this method must be called
180 before the channels are created.
181
182 Parameters:
183 -----------
184 **kw : optional
185 keyword arguments that are passed down to build the kernel_cmd
186 and launching the kernel (e.g. Popen kwargs).
187 """
188 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
189 raise RuntimeError("Can only launch a kernel on a local interface. "
190 "Make sure that the '*_address' attributes are "
191 "configured properly. "
192 "Currently valid addresses are: %s"%LOCAL_IPS
193 )
194
195 # write connection file / get default ports
196 self.write_connection_file()
197
198 # save kwargs for use in restart
199 self._launch_args = kw.copy()
200 # build the Popen cmd
201 kernel_cmd = self.format_kernel_cmd(**kw)
202 # launch the kernel subprocess
203 self.kernel = self._launch_kernel(kernel_cmd,
204 ipython_kernel=self.ipython_kernel,
205 **kw)
206 self.start_restarter()
207 self._connect_control_socket()
208
209 def _send_shutdown_request(self, restart=False):
210 """TODO: send a shutdown request via control channel"""
211 content = dict(restart=restart)
212 msg = self.session.msg("shutdown_request", content=content)
213 self.session.send(self._control_socket, msg)
214
215 def shutdown_kernel(self, now=False, restart=False):
216 """Attempts to the stop the kernel process cleanly.
217
218 This attempts to shutdown the kernels cleanly by:
219
220 1. Sending it a shutdown message over the shell channel.
221 2. If that fails, the kernel is shutdown forcibly by sending it
222 a signal.
223
224 Parameters:
225 -----------
226 now : bool
227 Should the kernel be forcible killed *now*. This skips the
228 first, nice shutdown attempt.
229 restart: bool
230 Will this kernel be restarted after it is shutdown. When this
231 is True, connection files will not be cleaned up.
232 """
233 # Stop monitoring for restarting while we shutdown.
234 self.stop_restarter()
235
236 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
237 if sys.platform == 'win32':
238 self._kill_kernel()
239 return
240
241 if now:
242 if self.has_kernel:
243 self._kill_kernel()
244 else:
245 # Don't send any additional kernel kill messages immediately, to give
246 # the kernel a chance to properly execute shutdown actions. Wait for at
247 # most 1s, checking every 0.1s.
248 self._send_shutdown_request(restart=restart)
249 for i in range(10):
250 if self.is_alive():
251 time.sleep(0.1)
252 else:
253 break
254 else:
255 # OK, we've waited long enough.
256 if self.has_kernel:
257 self._kill_kernel()
258
259 if not restart:
260 self.cleanup_connection_file()
261 self.cleanup_ipc_files()
262 else:
263 self.cleanup_ipc_files()
264
265 def restart_kernel(self, now=False, **kw):
266 """Restarts a kernel with the arguments that were used to launch it.
267
268 If the old kernel was launched with random ports, the same ports will be
269 used for the new kernel. The same connection file is used again.
270
271 Parameters
272 ----------
273 now : bool, optional
274 If True, the kernel is forcefully restarted *immediately*, without
275 having a chance to do any cleanup action. Otherwise the kernel is
276 given 1s to clean up before a forceful restart is issued.
277
278 In all cases the kernel is restarted, the only difference is whether
279 it is given a chance to perform a clean shutdown or not.
280
281 **kw : optional
282 Any options specified here will overwrite those used to launch the
283 kernel.
284 """
285 if self._launch_args is None:
286 raise RuntimeError("Cannot restart the kernel. "
287 "No previous call to 'start_kernel'.")
288 else:
289 # Stop currently running kernel.
290 self.shutdown_kernel(now=now, restart=True)
291
292 # Start new kernel.
293 self._launch_args.update(kw)
294 self.start_kernel(**self._launch_args)
295
296 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
297 # unless there is some delay here.
298 if sys.platform == 'win32':
299 time.sleep(0.2)
300
301 @property
302 def has_kernel(self):
303 """Has a kernel been started that we are managing."""
304 return self.kernel is not None
305
306 def _kill_kernel(self):
307 """Kill the running kernel.
308
309 This is a private method, callers should use shutdown_kernel(now=True).
310 """
311 if self.has_kernel:
312
313 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
314 # TerminateProcess() on Win32).
315 try:
316 self.kernel.kill()
317 except OSError as e:
318 # In Windows, we will get an Access Denied error if the process
319 # has already terminated. Ignore it.
320 if sys.platform == 'win32':
321 if e.winerror != 5:
322 raise
323 # On Unix, we may get an ESRCH error if the process has already
324 # terminated. Ignore it.
325 else:
326 from errno import ESRCH
327 if e.errno != ESRCH:
328 raise
329
330 # Block until the kernel terminates.
331 self.kernel.wait()
332 self.kernel = None
333 else:
334 raise RuntimeError("Cannot kill kernel. No kernel is running!")
335
336 def interrupt_kernel(self):
337 """Interrupts the kernel by sending it a signal.
338
339 Unlike ``signal_kernel``, this operation is well supported on all
340 platforms.
341 """
342 if self.has_kernel:
343 if sys.platform == 'win32':
344 from .zmq.parentpoller import ParentPollerWindows as Poller
345 Poller.send_interrupt(self.kernel.win32_interrupt_event)
346 else:
347 self.kernel.send_signal(signal.SIGINT)
348 else:
349 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
350
351 def signal_kernel(self, signum):
352 """Sends a signal to the kernel.
353
354 Note that since only SIGTERM is supported on Windows, this function is
355 only useful on Unix systems.
356 """
357 if self.has_kernel:
358 self.kernel.send_signal(signum)
359 else:
360 raise RuntimeError("Cannot signal kernel. No kernel is running!")
361
362 def is_alive(self):
363 """Is the kernel process still running?"""
364 if self.has_kernel:
365 if self.kernel.poll() is None:
366 return True
367 else:
368 return False
369 else:
370 # we don't have a kernel
371 return False
372
373
374 #-----------------------------------------------------------------------------
375 # ABC Registration
376 #-----------------------------------------------------------------------------
377
378 KernelManagerABC.register(KernelManager)
379
@@ -0,0 +1,114 b''
1 """A basic kernel monitor with autorestarting.
2
3 This watches a kernel's state using KernelManager.is_alive and auto
4 restarts the kernel if it dies.
5
6 It is an incomplete base class, and must be subclassed.
7 """
8
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
16 #-----------------------------------------------------------------------------
17 # Imports
18 #-----------------------------------------------------------------------------
19
20 from IPython.config.configurable import LoggingConfigurable
21 from IPython.utils.traitlets import (
22 Instance, Float, Dict, Bool, Integer,
23 )
24
25 #-----------------------------------------------------------------------------
26 # Code
27 #-----------------------------------------------------------------------------
28
29 class KernelRestarter(LoggingConfigurable):
30 """Monitor and autorestart a kernel."""
31
32 kernel_manager = Instance('IPython.kernel.KernelManager')
33
34 time_to_dead = Float(3.0, config=True,
35 help="""Kernel heartbeat interval in seconds."""
36 )
37
38 restart_limit = Integer(5, config=True,
39 help="""The number of consecutive autorestarts before the kernel is presumed dead."""
40 )
41 _restarting = Bool(False)
42 _restart_count = Integer(0)
43
44 callbacks = Dict()
45 def _callbacks_default(self):
46 return dict(restart=[], dead=[])
47
48 def start(self):
49 """Start the polling of the kernel."""
50 raise NotImplementedError("Must be implemented in a subclass")
51
52 def stop(self):
53 """Stop the kernel polling."""
54 raise NotImplementedError("Must be implemented in a subclass")
55
56 def add_callback(self, f, event='restart'):
57 """register a callback to fire on a particular event
58
59 Possible values for event:
60
61 'restart' (default): kernel has died, and will be restarted.
62 'dead': restart has failed, kernel will be left dead.
63
64 """
65 self.callbacks[event].append(f)
66
67 def remove_callback(self, f, event='restart'):
68 """unregister a callback to fire on a particular event
69
70 Possible values for event:
71
72 'restart' (default): kernel has died, and will be restarted.
73 'dead': restart has failed, kernel will be left dead.
74
75 """
76 try:
77 self.callbacks[event].remove(f)
78 except ValueError:
79 pass
80
81 def _fire_callbacks(self, event):
82 """fire our callbacks for a particular event"""
83 for callback in self.callbacks[event]:
84 try:
85 callback()
86 except Exception as e:
87 self.log.error("KernelRestarter: %s callback %r failed", event, callback, exc_info=True)
88
89 def poll(self):
90 self.log.debug('Polling kernel...')
91 if not self.kernel_manager.is_alive():
92 if self._restarting:
93 self._restart_count += 1
94 else:
95 self._restart_count = 1
96
97 if self._restart_count >= self.restart_limit:
98 self.log.warn("KernelRestarter: restart failed")
99 self._fire_callbacks('dead')
100 self._restarting = False
101 self._restart_count = 0
102 self.stop()
103 else:
104 self.log.info('KernelRestarter: restarting kernel (%i/%i)',
105 self._restart_count,
106 self.restart_limit
107 )
108 self._fire_callbacks('restart')
109 self.kernel_manager.restart_kernel(now=True)
110 self._restarting = True
111 else:
112 if self._restarting:
113 self.log.debug("KernelRestarter: restart apparently succeeded")
114 self._restarting = False
@@ -0,0 +1,45 b''
1 import os
2
3 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
4 from IPython.frontend.qt.inprocess import QtInProcessKernelManager
5 from IPython.lib import guisupport
6
7
8 def print_process_id():
9 print 'Process ID is:', os.getpid()
10
11
12 def main():
13 # Print the ID of the main process
14 print_process_id()
15
16 app = guisupport.get_app_qt4()
17
18 # Create an in-process kernel
19 # >>> print_process_id()
20 # will print the same process ID as the main process
21 kernel_manager = QtInProcessKernelManager()
22 kernel_manager.start_kernel()
23 kernel = kernel_manager.kernel
24 kernel.gui = 'qt4'
25 kernel.shell.push({'foo': 43, 'print_process_id': print_process_id})
26
27 kernel_client = kernel_manager.client()
28 kernel_client.start_channels()
29
30 def stop():
31 kernel_client.stop_channels()
32 kernel_manager.shutdown_kernel()
33 app.exit()
34
35 control = RichIPythonWidget()
36 control.kernel_manager = kernel_manager
37 control.kernel_client = kernel_client
38 control.exit_requested.connect(stop)
39 control.show()
40
41 guisupport.start_event_loop_qt4(app)
42
43
44 if __name__ == '__main__':
45 main()
@@ -0,0 +1,30 b''
1 import os
2
3 from IPython.kernel.inprocess import InProcessKernelManager
4 from IPython.frontend.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
5
6
7 def print_process_id():
8 print 'Process ID is:', os.getpid()
9
10
11 def main():
12 print_process_id()
13
14 # Create an in-process kernel
15 # >>> print_process_id()
16 # will print the same process ID as the main process
17 kernel_manager = InProcessKernelManager()
18 kernel_manager.start_kernel()
19 kernel = kernel_manager.kernel
20 kernel.gui = 'qt4'
21 kernel.shell.push({'foo': 43, 'print_process_id': print_process_id})
22 client = kernel_manager.client()
23 client.start_channels()
24
25 shell = ZMQTerminalInteractiveShell(manager=kernel_manager, client=client)
26 shell.mainloop()
27
28
29 if __name__ == '__main__':
30 main()
@@ -421,11 +421,11 b' class InteractiveShell(SingletonConfigurable):'
421 421
422 422 def __init__(self, config=None, ipython_dir=None, profile_dir=None,
423 423 user_module=None, user_ns=None,
424 custom_exceptions=((), None)):
424 custom_exceptions=((), None), **kwargs):
425 425
426 426 # This is where traits with a config_key argument are updated
427 427 # from the values on config.
428 super(InteractiveShell, self).__init__(config=config)
428 super(InteractiveShell, self).__init__(config=config, **kwargs)
429 429 self.configurables = [self]
430 430
431 431 # These are relatively independent and stateless
@@ -34,8 +34,8 b' import uuid'
34 34 from IPython.config.application import boolean_flag
35 35 from IPython.config.configurable import Configurable
36 36 from IPython.core.profiledir import ProfileDir
37 from IPython.kernel.blockingkernelmanager import BlockingKernelManager
38 from IPython.kernel.kernelmanager import KernelManager
37 from IPython.kernel.blocking import BlockingKernelClient
38 from IPython.kernel import KernelManager
39 39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
40 40 from IPython.utils.path import filefind
41 41 from IPython.utils.py3compat import str_to_bytes
@@ -144,7 +144,8 b' class IPythonConsoleApp(Configurable):'
144 144 classes = classes
145 145 flags = Dict(flags)
146 146 aliases = Dict(aliases)
147 kernel_manager_class = BlockingKernelManager
147 kernel_manager_class = KernelManager
148 kernel_client_class = BlockingKernelClient
148 149
149 150 kernel_argv = List(Unicode)
150 151 # frontend flags&aliases to be stripped when building kernel_argv
@@ -328,6 +329,9 b' class IPythonConsoleApp(Configurable):'
328 329
329 330 def init_kernel_manager(self):
330 331 # Don't let Qt or ZMQ swallow KeyboardInterupts.
332 if self.existing:
333 self.kernel_manager = None
334 return
331 335 signal.signal(signal.SIGINT, signal.SIG_DFL)
332 336
333 337 # Create a KernelManager and start a kernel.
@@ -339,15 +343,39 b' class IPythonConsoleApp(Configurable):'
339 343 connection_file=self.connection_file,
340 344 config=self.config,
341 345 )
342 # start the kernel
343 if not self.existing:
344 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
345 atexit.register(self.kernel_manager.cleanup_ipc_files)
346 elif self.sshserver:
346 self.kernel_manager.client_factory = self.kernel_client_class
347 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
348 atexit.register(self.kernel_manager.cleanup_ipc_files)
349
350 if self.sshserver:
347 351 # ssh, write new connection file
348 352 self.kernel_manager.write_connection_file()
353
354 # in case KM defaults / ssh writing changes things:
355 km = self.kernel_manager
356 self.shell_port=km.shell_port
357 self.iopub_port=km.iopub_port
358 self.stdin_port=km.stdin_port
359 self.hb_port=km.hb_port
360 self.connection_file = km.connection_file
361
349 362 atexit.register(self.kernel_manager.cleanup_connection_file)
350 self.kernel_manager.start_channels()
363
364 def init_kernel_client(self):
365 if self.kernel_manager is not None:
366 self.kernel_client = self.kernel_manager.client()
367 else:
368 self.kernel_client = self.kernel_client_class(
369 shell_port=self.shell_port,
370 iopub_port=self.iopub_port,
371 stdin_port=self.stdin_port,
372 hb_port=self.hb_port,
373 connection_file=self.connection_file,
374 config=self.config,
375 )
376
377 self.kernel_client.start_channels()
378
351 379
352 380
353 381 def initialize(self, argv=None):
@@ -359,4 +387,5 b' class IPythonConsoleApp(Configurable):'
359 387 default_secure(self.config)
360 388 self.init_ssh()
361 389 self.init_kernel_manager()
390 self.init_kernel_client()
362 391
@@ -407,6 +407,9 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
407 407 return jsonapi.dumps(msg, default=date_default)
408 408
409 409 def _on_zmq_reply(self, msg_list):
410 # Sometimes this gets triggered when the on_close method is scheduled in the
411 # eventloop but hasn't been called.
412 if self.stream.closed(): return
410 413 try:
411 414 msg = self._reserialize_reply(msg_list)
412 415 except Exception:
@@ -466,10 +469,7 b' class AuthenticatedZMQStreamHandler(ZMQStreamHandler):'
466 469 class IOPubHandler(AuthenticatedZMQStreamHandler):
467 470
468 471 def initialize(self, *args, **kwargs):
469 self._kernel_alive = True
470 self._beating = False
471 472 self.iopub_stream = None
472 self.hb_stream = None
473 473
474 474 def on_first_message(self, msg):
475 475 try:
@@ -478,12 +478,11 b' class IOPubHandler(AuthenticatedZMQStreamHandler):'
478 478 self.close()
479 479 return
480 480 km = self.application.kernel_manager
481 self.time_to_dead = km.time_to_dead
482 self.first_beat = km.first_beat
483 481 kernel_id = self.kernel_id
482 km.add_restart_callback(kernel_id, self.on_kernel_restarted)
483 km.add_restart_callback(kernel_id, self.on_restart_failed, 'dead')
484 484 try:
485 self.iopub_stream = km.create_iopub_stream(kernel_id)
486 self.hb_stream = km.create_hb_stream(kernel_id)
485 self.iopub_stream = km.connect_iopub(kernel_id)
487 486 except web.HTTPError:
488 487 # WebSockets don't response to traditional error codes so we
489 488 # close the connection.
@@ -492,81 +491,39 b' class IOPubHandler(AuthenticatedZMQStreamHandler):'
492 491 self.close()
493 492 else:
494 493 self.iopub_stream.on_recv(self._on_zmq_reply)
495 self.start_hb(self.kernel_died)
496 494
497 495 def on_message(self, msg):
498 496 pass
499 497
498 def _send_status_message(self, status):
499 msg = self.session.msg("status",
500 {'execution_state': status}
501 )
502 self.write_message(jsonapi.dumps(msg, default=date_default))
503
504 def on_kernel_restarted(self):
505 logging.warn("kernel %s restarted", self.kernel_id)
506 self._send_status_message('restarting')
507
508 def on_restart_failed(self):
509 logging.error("kernel %s restarted failed!", self.kernel_id)
510 self._send_status_message('dead')
511
500 512 def on_close(self):
501 513 # This method can be called twice, once by self.kernel_died and once
502 514 # from the WebSocket close event. If the WebSocket connection is
503 515 # closed before the ZMQ streams are setup, they could be None.
504 self.stop_hb()
516 km = self.application.kernel_manager
517 if self.kernel_id in km:
518 km.remove_restart_callback(
519 self.kernel_id, self.on_kernel_restarted,
520 )
521 km.remove_restart_callback(
522 self.kernel_id, self.on_restart_failed, 'dead',
523 )
505 524 if self.iopub_stream is not None and not self.iopub_stream.closed():
506 525 self.iopub_stream.on_recv(None)
507 526 self.iopub_stream.close()
508 if self.hb_stream is not None and not self.hb_stream.closed():
509 self.hb_stream.close()
510
511 def start_hb(self, callback):
512 """Start the heartbeating and call the callback if the kernel dies."""
513 if not self._beating:
514 self._kernel_alive = True
515
516 def ping_or_dead():
517 self.hb_stream.flush()
518 if self._kernel_alive:
519 self._kernel_alive = False
520 self.hb_stream.send(b'ping')
521 # flush stream to force immediate socket send
522 self.hb_stream.flush()
523 else:
524 try:
525 callback()
526 except:
527 pass
528 finally:
529 self.stop_hb()
530
531 def beat_received(msg):
532 self._kernel_alive = True
533
534 self.hb_stream.on_recv(beat_received)
535 loop = ioloop.IOLoop.instance()
536 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000, loop)
537 loop.add_timeout(time.time()+self.first_beat, self._really_start_hb)
538 self._beating= True
539
540 def _really_start_hb(self):
541 """callback for delayed heartbeat start
542
543 Only start the hb loop if we haven't been closed during the wait.
544 """
545 if self._beating and not self.hb_stream.closed():
546 self._hb_periodic_callback.start()
547
548 def stop_hb(self):
549 """Stop the heartbeating and cancel all related callbacks."""
550 if self._beating:
551 self._beating = False
552 self._hb_periodic_callback.stop()
553 if not self.hb_stream.closed():
554 self.hb_stream.on_recv(None)
555
556 def _delete_kernel_data(self):
557 """Remove the kernel data and notebook mapping."""
558 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
559
560 def kernel_died(self):
561 self._delete_kernel_data()
562 self.application.log.error("Kernel died: %s" % self.kernel_id)
563 self.write_message(
564 {'header': {'msg_type': 'status'},
565 'parent_header': {},
566 'content': {'execution_state':'dead'}
567 }
568 )
569 self.on_close()
570 527
571 528
572 529 class ShellHandler(AuthenticatedZMQStreamHandler):
@@ -584,7 +541,7 b' class ShellHandler(AuthenticatedZMQStreamHandler):'
584 541 self.max_msg_size = km.max_msg_size
585 542 kernel_id = self.kernel_id
586 543 try:
587 self.shell_stream = km.create_shell_stream(kernel_id)
544 self.shell_stream = km.connect_shell(kernel_id)
588 545 except web.HTTPError:
589 546 # WebSockets don't response to traditional error codes so we
590 547 # close the connection.
@@ -20,8 +20,9 b' from tornado import web'
20 20
21 21 from IPython.kernel.multikernelmanager import MultiKernelManager
22 22 from IPython.utils.traitlets import (
23 Dict, List, Unicode, Float, Integer,
23 Dict, List, Unicode, Integer,
24 24 )
25
25 26 #-----------------------------------------------------------------------------
26 27 # Classes
27 28 #-----------------------------------------------------------------------------
@@ -30,11 +31,11 b' from IPython.utils.traitlets import ('
30 31 class MappingKernelManager(MultiKernelManager):
31 32 """A KernelManager that handles notebok mapping and HTTP error handling"""
32 33
34 def _kernel_manager_class_default(self):
35 return "IPython.kernel.ioloop.IOLoopKernelManager"
36
33 37 kernel_argv = List(Unicode)
34 38
35 time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")
36 first_beat = Float(5.0, config=True, help="Delay (in seconds) before sending first heartbeat.")
37
38 39 max_msg_size = Integer(65536, config=True, help="""
39 40 The max raw message size accepted from the browser
40 41 over a WebSocket connection.
@@ -57,11 +58,10 b' class MappingKernelManager(MultiKernelManager):'
57 58
58 59 def notebook_for_kernel(self, kernel_id):
59 60 """Return the notebook_id for a kernel_id or None."""
60 notebook_ids = [k for k, v in self._notebook_mapping.iteritems() if v == kernel_id]
61 if len(notebook_ids) == 1:
62 return notebook_ids[0]
63 else:
64 return None
61 for notebook_id, kid in self._notebook_mapping.iteritems():
62 if kernel_id == kid:
63 return notebook_id
64 return None
65 65
66 66 def delete_mapping_for_kernel(self, kernel_id):
67 67 """Remove the kernel/notebook mapping for kernel_id."""
@@ -69,8 +69,14 b' class MappingKernelManager(MultiKernelManager):'
69 69 if notebook_id is not None:
70 70 del self._notebook_mapping[notebook_id]
71 71
72 def _handle_kernel_died(self, kernel_id):
73 """notice that a kernel died"""
74 self.log.warn("Kernel %s died, removing from map.", kernel_id)
75 self.delete_mapping_for_kernel(kernel_id)
76 self.remove_kernel(kernel_id, now=True)
77
72 78 def start_kernel(self, notebook_id=None, **kwargs):
73 """Start a kernel for a notebok an return its kernel_id.
79 """Start a kernel for a notebook an return its kernel_id.
74 80
75 81 Parameters
76 82 ----------
@@ -86,46 +92,22 b' class MappingKernelManager(MultiKernelManager):'
86 92 self.set_kernel_for_notebook(notebook_id, kernel_id)
87 93 self.log.info("Kernel started: %s" % kernel_id)
88 94 self.log.debug("Kernel args: %r" % kwargs)
95 # register callback for failed auto-restart
96 self.add_restart_callback(kernel_id,
97 lambda : self._handle_kernel_died(kernel_id),
98 'dead',
99 )
89 100 else:
90 101 self.log.info("Using existing kernel: %s" % kernel_id)
102
91 103 return kernel_id
92 104
93 105 def shutdown_kernel(self, kernel_id, now=False):
94 """Shutdown a kernel and remove its notebook association."""
95 self._check_kernel_id(kernel_id)
96 super(MappingKernelManager, self).shutdown_kernel(
97 kernel_id, now=now
98 )
106 """Shutdown a kernel by kernel_id"""
107 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
99 108 self.delete_mapping_for_kernel(kernel_id)
100 self.log.info("Kernel shutdown: %s" % kernel_id)
101
102 def interrupt_kernel(self, kernel_id):
103 """Interrupt a kernel."""
104 self._check_kernel_id(kernel_id)
105 super(MappingKernelManager, self).interrupt_kernel(kernel_id)
106 self.log.info("Kernel interrupted: %s" % kernel_id)
107
108 def restart_kernel(self, kernel_id):
109 """Restart a kernel while keeping clients connected."""
110 self._check_kernel_id(kernel_id)
111 super(MappingKernelManager, self).restart_kernel(kernel_id)
112 self.log.info("Kernel restarted: %s" % kernel_id)
113
114 def create_iopub_stream(self, kernel_id):
115 """Create a new iopub stream."""
116 self._check_kernel_id(kernel_id)
117 return super(MappingKernelManager, self).create_iopub_stream(kernel_id)
118
119 def create_shell_stream(self, kernel_id):
120 """Create a new shell stream."""
121 self._check_kernel_id(kernel_id)
122 return super(MappingKernelManager, self).create_shell_stream(kernel_id)
123
124 def create_hb_stream(self, kernel_id):
125 """Create a new hb stream."""
126 self._check_kernel_id(kernel_id)
127 return super(MappingKernelManager, self).create_hb_stream(kernel_id)
128 109
110 # override _check_kernel_id to raise 404 instead of KeyError
129 111 def _check_kernel_id(self, kernel_id):
130 112 """Check a that a kernel_id exists and raise 404 if not."""
131 113 if kernel_id not in self:
@@ -104,8 +104,6 b' var IPython = (function (IPython) {'
104 104 this.ws_url = json.ws_url;
105 105 this.kernel_url = this.base_url + "/" + this.kernel_id;
106 106 this.start_channels();
107 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
108 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
109 107 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
110 108 };
111 109
@@ -165,6 +163,8 b' var IPython = (function (IPython) {'
165 163 that.iopub_channel.onclose = ws_closed_late;
166 164 }
167 165 }, 1000);
166 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
167 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
168 168 };
169 169
170 170 /**
@@ -418,6 +418,8 b' var IPython = (function (IPython) {'
418 418 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
419 419 } else if (content.execution_state === 'idle') {
420 420 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
421 } else if (content.execution_state === 'restarting') {
422 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
421 423 } else if (content.execution_state === 'dead') {
422 424 this.stop_channels();
423 425 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
@@ -84,7 +84,7 b' var IPython = (function (IPython) {'
84 84
85 85 $([IPython.events]).on('status_restarting.Kernel',function () {
86 86 IPython.save_widget.update_document_title();
87 knw.set_message("Restarting kernel",1000);
87 knw.set_message("Restarting kernel", 2000);
88 88 });
89 89
90 90 $([IPython.events]).on('status_interrupting.Kernel',function () {
@@ -93,9 +93,10 b' var IPython = (function (IPython) {'
93 93
94 94 $([IPython.events]).on('status_dead.Kernel',function () {
95 95 var dialog = $('<div/>');
96 dialog.html('The kernel has died, would you like to restart it?' +
97 ' If you do not restart the kernel, you will be able to save' +
98 ' the notebook, but running code will not work until the notebook' +
96 dialog.html('The kernel has died, and the automatic restart has failed.' +
97 ' It is possible the kernel cannot be restarted.' +
98 ' If you are not able to restart the kernel, you will still be able to save' +
99 ' the notebook, but running code will no longer work until the notebook' +
99 100 ' is reopened.'
100 101 );
101 102 $(document).append(dialog);
@@ -105,7 +106,7 b' var IPython = (function (IPython) {'
105 106 title: "Dead kernel",
106 107 close: function(event, ui) {$(this).dialog('destroy').remove();},
107 108 buttons : {
108 "Restart": function () {
109 "Manual Restart": function () {
109 110 $([IPython.events]).trigger('status_restarting.Kernel');
110 111 IPython.notebook.start_kernel();
111 112 $(this).dialog('close');
@@ -12,56 +12,73 b' class BaseFrontendMixin(object):'
12 12 #---------------------------------------------------------------------------
13 13 # 'BaseFrontendMixin' concrete interface
14 14 #---------------------------------------------------------------------------
15
16 def _get_kernel_manager(self):
17 """ Returns the current kernel manager.
15 _kernel_client = None
16 _kernel_manager = None
17
18 @property
19 def kernel_client(self):
20 """Returns the current kernel client."""
21 return self._kernel_client
22
23 @kernel_client.setter
24 def kernel_client(self, kernel_client):
25 """Disconnect from the current kernel client (if any) and set a new
26 kernel client.
18 27 """
19 return self._kernel_manager
20
21 def _set_kernel_manager(self, kernel_manager):
22 """ Disconnect from the current kernel manager (if any) and set a new
23 kernel manager.
24 """
25 # Disconnect the old kernel manager, if necessary.
26 old_manager = self._kernel_manager
27 if old_manager is not None:
28 old_manager.started_kernel.disconnect(self._started_kernel)
29 old_manager.started_channels.disconnect(self._started_channels)
30 old_manager.stopped_channels.disconnect(self._stopped_channels)
31
32 # Disconnect the old kernel manager's channels.
33 old_manager.iopub_channel.message_received.disconnect(self._dispatch)
34 old_manager.shell_channel.message_received.disconnect(self._dispatch)
35 old_manager.stdin_channel.message_received.disconnect(self._dispatch)
36 old_manager.hb_channel.kernel_died.disconnect(
28 # Disconnect the old kernel client, if necessary.
29 old_client = self._kernel_client
30 if old_client is not None:
31 old_client.started_channels.disconnect(self._started_channels)
32 old_client.stopped_channels.disconnect(self._stopped_channels)
33
34 # Disconnect the old kernel client's channels.
35 old_client.iopub_channel.message_received.disconnect(self._dispatch)
36 old_client.shell_channel.message_received.disconnect(self._dispatch)
37 old_client.stdin_channel.message_received.disconnect(self._dispatch)
38 old_client.hb_channel.kernel_died.disconnect(
37 39 self._handle_kernel_died)
38 40
39 # Handle the case where the old kernel manager is still listening.
40 if old_manager.channels_running:
41 # Handle the case where the old kernel client is still listening.
42 if old_client.channels_running:
41 43 self._stopped_channels()
42 44
43 # Set the new kernel manager.
44 self._kernel_manager = kernel_manager
45 if kernel_manager is None:
45 # Set the new kernel client.
46 self._kernel_client = kernel_client
47 if kernel_client is None:
46 48 return
47 49
48 # Connect the new kernel manager.
49 kernel_manager.started_kernel.connect(self._started_kernel)
50 kernel_manager.started_channels.connect(self._started_channels)
51 kernel_manager.stopped_channels.connect(self._stopped_channels)
50 # Connect the new kernel client.
51 kernel_client.started_channels.connect(self._started_channels)
52 kernel_client.stopped_channels.connect(self._stopped_channels)
52 53
53 # Connect the new kernel manager's channels.
54 kernel_manager.iopub_channel.message_received.connect(self._dispatch)
55 kernel_manager.shell_channel.message_received.connect(self._dispatch)
56 kernel_manager.stdin_channel.message_received.connect(self._dispatch)
57 kernel_manager.hb_channel.kernel_died.connect(self._handle_kernel_died)
54 # Connect the new kernel client's channels.
55 kernel_client.iopub_channel.message_received.connect(self._dispatch)
56 kernel_client.shell_channel.message_received.connect(self._dispatch)
57 kernel_client.stdin_channel.message_received.connect(self._dispatch)
58 # hb_channel
59 kernel_client.hb_channel.kernel_died.connect(self._handle_kernel_died)
58 60
59 # Handle the case where the kernel manager started channels before
61 # Handle the case where the kernel client started channels before
60 62 # we connected.
61 if kernel_manager.channels_running:
63 if kernel_client.channels_running:
62 64 self._started_channels()
63 65
64 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
66 @property
67 def kernel_manager(self):
68 """The kernel manager, if any"""
69 return self._kernel_manager
70
71 @kernel_manager.setter
72 def kernel_manager(self, kernel_manager):
73 old_man = self._kernel_manager
74 if old_man is not None:
75 old_man.kernel_restarted.disconnect(self._handle_kernel_restarted)
76
77 self._kernel_manager = kernel_manager
78 if kernel_manager is None:
79 return
80
81 kernel_manager.kernel_restarted.connect(self._handle_kernel_restarted)
65 82
66 83 #---------------------------------------------------------------------------
67 84 # 'BaseFrontendMixin' abstract interface
@@ -71,8 +88,9 b' class BaseFrontendMixin(object):'
71 88 """ This is called when the ``kernel_died`` signal is emitted.
72 89
73 90 This method is called when the kernel heartbeat has not been
74 active for a certain amount of time. The typical action will be to
75 give the user the option of restarting the kernel.
91 active for a certain amount of time.
92 This is a strictly passive notification -
93 the kernel is likely being restarted by its KernelManager.
76 94
77 95 Parameters
78 96 ----------
@@ -80,6 +98,17 b' class BaseFrontendMixin(object):'
80 98 The time since the heartbeat was last received.
81 99 """
82 100
101 def _handle_kernel_restarted(self):
102 """ This is called when the ``kernel_restarted`` signal is emitted.
103
104 This method is called when the kernel has been restarted by the
105 autorestart mechanism.
106
107 Parameters
108 ----------
109 since_last_heartbeat : float
110 The time since the heartbeat was last received.
111 """
83 112 def _started_kernel(self):
84 113 """Called when the KernelManager starts (or restarts) the kernel subprocess.
85 114 Channels may or may not be running at this point.
@@ -112,7 +141,7 b' class BaseFrontendMixin(object):'
112 141 """ Returns whether a reply from the kernel originated from a request
113 142 from this frontend.
114 143 """
115 session = self._kernel_manager.session.session
144 session = self._kernel_client.session.session
116 145 parent = msg['parent_header']
117 146 if not parent:
118 147 # if the message has no parent, assume it is meant for all frontends
@@ -1,13 +1,18 b''
1 """ Defines a KernelManager that provides signals and slots.
1 """ Defines a KernelClient that provides signals and slots.
2 2 """
3 3
4 # Local imports.
4 # Local imports
5 5 from IPython.utils.traitlets import Type
6 from IPython.kernel.kernelmanager import ShellChannel, IOPubChannel, \
7 StdInChannel, HBChannel, KernelManager
8 from base_kernelmanager import QtShellChannelMixin, QtIOPubChannelMixin, \
9 QtStdInChannelMixin, QtHBChannelMixin, QtKernelManagerMixin
6 from IPython.kernel.channels import (
7 ShellChannel, IOPubChannel, StdInChannel, HBChannel
8 )
9 from IPython.kernel import KernelClient
10 10
11 from .kernel_mixins import (
12 QtShellChannelMixin, QtIOPubChannelMixin,
13 QtStdInChannelMixin, QtHBChannelMixin,
14 QtKernelClientMixin
15 )
11 16
12 17 class QtShellChannel(QtShellChannelMixin, ShellChannel):
13 18 pass
@@ -22,8 +27,8 b' class QtHBChannel(QtHBChannelMixin, HBChannel):'
22 27 pass
23 28
24 29
25 class QtKernelManager(QtKernelManagerMixin, KernelManager):
26 """ A KernelManager that provides signals and slots.
30 class QtKernelClient(QtKernelClientMixin, KernelClient):
31 """ A KernelClient that provides signals and slots.
27 32 """
28 33
29 34 iopub_channel_class = Type(QtIOPubChannel)
@@ -148,6 +148,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
148 148 self._highlighter = FrontendHighlighter(self)
149 149 self._input_splitter = self._input_splitter_class()
150 150 self._kernel_manager = None
151 self._kernel_client = None
151 152 self._request_info = {}
152 153 self._request_info['execute'] = {};
153 154 self._callback_dict = {}
@@ -215,7 +216,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
215 216
216 217 See parent class :meth:`execute` docstring for full details.
217 218 """
218 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
219 msg_id = self.kernel_client.execute(source, hidden)
219 220 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
220 221 self._hidden = hidden
221 222 if not hidden:
@@ -357,7 +358,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
357 358 # generate uuid, which would be used as an indication of whether or
358 359 # not the unique request originated from here (can use msg id ?)
359 360 local_uuid = str(uuid.uuid1())
360 msg_id = self.kernel_manager.shell_channel.execute('',
361 msg_id = self.kernel_client.execute('',
361 362 silent=True, user_expressions={ local_uuid:expr })
362 363 self._callback_dict[local_uuid] = callback
363 364 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
@@ -400,7 +401,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
400 401 if info and info.kind == 'user' and not self._hidden:
401 402 # Make sure that all output from the SUB channel has been processed
402 403 # before writing a new prompt.
403 self.kernel_manager.iopub_channel.flush()
404 self.kernel_client.iopub_channel.flush()
404 405
405 406 # Reset the ANSI style information to prevent bad text in stdout
406 407 # from messing up our colors. We're not a true terminal so we're
@@ -435,27 +436,39 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
435 436
436 437 # Make sure that all output from the SUB channel has been processed
437 438 # before entering readline mode.
438 self.kernel_manager.iopub_channel.flush()
439 self.kernel_client.iopub_channel.flush()
439 440
440 441 def callback(line):
441 self.kernel_manager.stdin_channel.input(line)
442 self.kernel_client.stdin_channel.input(line)
442 443 if self._reading:
443 444 self.log.debug("Got second input request, assuming first was interrupted.")
444 445 self._reading = False
445 446 self._readline(msg['content']['prompt'], callback=callback)
446 447
448 def _kernel_restarted_message(self, died=True):
449 msg = "Kernel died, restarting" if died else "Kernel restarting"
450 self._append_html("<br>%s<hr><br>" % msg,
451 before_prompt=False
452 )
453
447 454 def _handle_kernel_died(self, since_last_heartbeat):
448 """ Handle the kernel's death by asking if the user wants to restart.
455 """Handle the kernel's death (if we do not own the kernel).
449 456 """
450 self.log.debug("kernel died: %s", since_last_heartbeat)
457 self.log.warn("kernel died: %s", since_last_heartbeat)
451 458 if self.custom_restart:
452 459 self.custom_restart_kernel_died.emit(since_last_heartbeat)
453 460 else:
454 message = 'The kernel heartbeat has been inactive for %.2f ' \
455 'seconds. Do you want to restart the kernel? You may ' \
456 'first want to check the network connection.' % \
457 since_last_heartbeat
458 self.restart_kernel(message, now=True)
461 self._kernel_restarted_message(died=True)
462 self.reset()
463
464 def _handle_kernel_restarted(self, died=True):
465 """Notice that the autorestarter restarted the kernel.
466
467 There's nothing to do but show a message.
468 """
469 self.log.warn("kernel restarted")
470 self._kernel_restarted_message(died=died)
471 self.reset()
459 472
460 473 def _handle_object_info_reply(self, rep):
461 474 """ Handle replies for call tips.
@@ -505,37 +518,42 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
505 518 def _handle_shutdown_reply(self, msg):
506 519 """ Handle shutdown signal, only if from other console.
507 520 """
508 self.log.debug("shutdown: %s", msg.get('content', ''))
521 self.log.warn("shutdown: %s", msg.get('content', ''))
522 restart = msg.get('content', {}).get('restart', False)
509 523 if not self._hidden and not self._is_from_this_session(msg):
510 if self._local_kernel:
511 if not msg['content']['restart']:
524 # got shutdown reply, request came from session other than ours
525 if restart:
526 # someone restarted the kernel, handle it
527 self._handle_kernel_restarted(died=False)
528 else:
529 # kernel was shutdown permanently
530 # this triggers exit_requested if the kernel was local,
531 # and a dialog if the kernel was remote,
532 # so we don't suddenly clear the qtconsole without asking.
533 if self._local_kernel:
512 534 self.exit_requested.emit(self)
513 535 else:
514 # we just got notified of a restart!
515 time.sleep(0.25) # wait 1/4 sec to reset
516 # lest the request for a new prompt
517 # goes to the old kernel
518 self.reset()
519 else: # remote kernel, prompt on Kernel shutdown/reset
520 title = self.window().windowTitle()
521 if not msg['content']['restart']:
536 title = self.window().windowTitle()
522 537 reply = QtGui.QMessageBox.question(self, title,
523 538 "Kernel has been shutdown permanently. "
524 539 "Close the Console?",
525 540 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
526 541 if reply == QtGui.QMessageBox.Yes:
527 542 self.exit_requested.emit(self)
528 else:
529 # XXX: remove message box in favor of using the
530 # clear_on_kernel_restart setting?
531 reply = QtGui.QMessageBox.question(self, title,
532 "Kernel has been reset. Clear the Console?",
533 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
534 if reply == QtGui.QMessageBox.Yes:
535 time.sleep(0.25) # wait 1/4 sec to reset
536 # lest the request for a new prompt
537 # goes to the old kernel
538 self.reset()
543
544 def _handle_status(self, msg):
545 """Handle status message"""
546 # This is where a busy/idle indicator would be triggered,
547 # when we make one.
548 state = msg['content'].get('execution_state', '')
549 if state == 'starting':
550 # kernel started while we were running
551 if self._executing:
552 self._handle_kernel_restarted(died=True)
553 elif state == 'idle':
554 pass
555 elif state == 'busy':
556 pass
539 557
540 558 def _started_channels(self):
541 559 """ Called when the KernelManager channels have started listening or
@@ -568,16 +586,15 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
568 586 if self.custom_interrupt:
569 587 self._reading = False
570 588 self.custom_interrupt_requested.emit()
571 elif self.kernel_manager.has_kernel:
589 elif self.kernel_manager:
572 590 self._reading = False
573 591 self.kernel_manager.interrupt_kernel()
574 592 else:
575 self._append_plain_text('Kernel process is either remote or '
576 'unspecified. Cannot interrupt.\n')
593 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
577 594
578 595 def reset(self, clear=False):
579 """ Resets the widget to its initial state if ``clear`` parameter or
580 ``clear_on_kernel_restart`` configuration setting is True, otherwise
596 """ Resets the widget to its initial state if ``clear`` parameter
597 is True, otherwise
581 598 prints a visual indication of the fact that the kernel restarted, but
582 599 does not clear the traces from previous usage of the kernel before it
583 600 was restarted. With ``clear=True``, it is similar to ``%clear``, but
@@ -589,15 +606,9 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
589 606 self._reading = False
590 607 self._highlighter.highlighting_on = False
591 608
592 if self.clear_on_kernel_restart or clear:
609 if clear:
593 610 self._control.clear()
594 611 self._append_plain_text(self.banner)
595 else:
596 self._append_plain_text("# restarting kernel...")
597 self._append_html("<hr><br>")
598 # XXX: Reprinting the full banner may be too much, but once #1680 is
599 # addressed, that will mitigate it.
600 #self._append_plain_text(self.banner)
601 612 # update output marker for stdout/stderr, so that startup
602 613 # messages appear after banner:
603 614 self._append_before_prompt_pos = self._get_cursor().position()
@@ -614,10 +625,11 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
614 625
615 626 if self.custom_restart:
616 627 self.custom_restart_requested.emit()
628 return
617 629
618 elif self.kernel_manager.has_kernel:
630 if self.kernel_manager:
619 631 # Pause the heart beat channel to prevent further warnings.
620 self.kernel_manager.hb_channel.pause()
632 self.kernel_client.hb_channel.pause()
621 633
622 634 # Prompt the user to restart the kernel. Un-pause the heartbeat if
623 635 # they decline. (If they accept, the heartbeat will be un-paused
@@ -634,21 +646,23 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
634 646 if do_restart:
635 647 try:
636 648 self.kernel_manager.restart_kernel(now=now)
637 except RuntimeError:
638 self._append_plain_text('Kernel started externally. '
639 'Cannot restart.\n',
640 before_prompt=True
641 )
649 except RuntimeError as e:
650 self._append_plain_text(
651 'Error restarting kernel: %s\n' % e,
652 before_prompt=True
653 )
642 654 else:
643 self.reset()
655 self._append_html("<br>Restarting kernel...\n<hr><br>",
656 before_prompt=True,
657 )
644 658 else:
645 self.kernel_manager.hb_channel.unpause()
659 self.kernel_client.hb_channel.unpause()
646 660
647 661 else:
648 self._append_plain_text('Kernel process is either remote or '
649 'unspecified. Cannot restart.\n',
650 before_prompt=True
651 )
662 self._append_plain_text(
663 'Cannot restart a Kernel I did not start\n',
664 before_prompt=True
665 )
652 666
653 667 #---------------------------------------------------------------------------
654 668 # 'FrontendWidget' protected interface
@@ -670,7 +684,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
670 684
671 685 # Send the metadata request to the kernel
672 686 name = '.'.join(context)
673 msg_id = self.kernel_manager.shell_channel.object_info(name)
687 msg_id = self.kernel_client.object_info(name)
674 688 pos = self._get_cursor().position()
675 689 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
676 690 return True
@@ -681,7 +695,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
681 695 context = self._get_context()
682 696 if context:
683 697 # Send the completion request to the kernel
684 msg_id = self.kernel_manager.shell_channel.complete(
698 msg_id = self.kernel_client.complete(
685 699 '.'.join(context), # text
686 700 self._get_input_buffer_cursor_line(), # line
687 701 self._get_input_buffer_cursor_column(), # cursor_pos
@@ -224,7 +224,7 b' class HistoryConsoleWidget(ConsoleWidget):'
224 224 return self._history[-n:]
225 225
226 226 def _request_update_session_history_length(self):
227 msg_id = self.kernel_manager.shell_channel.execute('',
227 msg_id = self.kernel_client.shell_channel.execute('',
228 228 silent=True,
229 229 user_expressions={
230 230 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
@@ -194,7 +194,7 b' class IPythonWidget(FrontendWidget):'
194 194 self._retrying_history_request = True
195 195 # wait out the kernel's queue flush, which is currently timed at 0.1s
196 196 time.sleep(0.25)
197 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
197 self.kernel_client.shell_channel.history(hist_access_type='tail',n=1000)
198 198 else:
199 199 self._retrying_history_request = False
200 200 return
@@ -261,7 +261,7 b' class IPythonWidget(FrontendWidget):'
261 261 """Reimplemented to make a history request and load %guiref."""
262 262 super(IPythonWidget, self)._started_channels()
263 263 self._load_guiref_magic()
264 self.kernel_manager.shell_channel.history(hist_access_type='tail',
264 self.kernel_client.shell_channel.history(hist_access_type='tail',
265 265 n=1000)
266 266
267 267 def _started_kernel(self):
@@ -269,12 +269,12 b' class IPythonWidget(FrontendWidget):'
269 269
270 270 Principally triggered by kernel restart.
271 271 """
272 if self.kernel_manager.shell_channel is not None:
272 if self.kernel_client.shell_channel is not None:
273 273 self._load_guiref_magic()
274 274
275 275 def _load_guiref_magic(self):
276 276 """Load %guiref magic."""
277 self.kernel_manager.shell_channel.execute('\n'.join([
277 self.kernel_client.shell_channel.execute('\n'.join([
278 278 "try:",
279 279 " _usage",
280 280 "except:",
@@ -330,7 +330,7 b' class IPythonWidget(FrontendWidget):'
330 330 text = ''
331 331
332 332 # Send the completion request to the kernel
333 msg_id = self.kernel_manager.shell_channel.complete(
333 msg_id = self.kernel_client.shell_channel.complete(
334 334 text, # text
335 335 self._get_input_buffer_cursor_line(), # line
336 336 self._get_input_buffer_cursor_column(), # cursor_pos
@@ -376,7 +376,7 b' class IPythonWidget(FrontendWidget):'
376 376 """
377 377 # If a number was not specified, make a prompt number request.
378 378 if number is None:
379 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
379 msg_id = self.kernel_client.shell_channel.execute('', silent=True)
380 380 info = self._ExecutionRequest(msg_id, 'prompt')
381 381 self._request_info['execute'][msg_id] = info
382 382 return
@@ -176,6 +176,7 b' class MainWindow(QtGui.QMainWindow):'
176 176 self.update_tab_bar_visibility()
177 177 return
178 178
179 kernel_client = closing_widget.kernel_client
179 180 kernel_manager = closing_widget.kernel_manager
180 181
181 182 if keepkernel is None and not closing_widget._confirm_exit:
@@ -183,7 +184,7 b' class MainWindow(QtGui.QMainWindow):'
183 184 # or leave it alone if we don't
184 185 keepkernel = closing_widget._existing
185 186 if keepkernel is None: #show prompt
186 if kernel_manager and kernel_manager.channels_running:
187 if kernel_client and kernel_client.channels_running:
187 188 title = self.window().windowTitle()
188 189 cancel = QtGui.QMessageBox.Cancel
189 190 okay = QtGui.QMessageBox.Ok
@@ -209,17 +210,17 b' class MainWindow(QtGui.QMainWindow):'
209 210 reply = box.exec_()
210 211 if reply == 1: # close All
211 212 for slave in slave_tabs:
212 background(slave.kernel_manager.stop_channels)
213 background(slave.kernel_client.stop_channels)
213 214 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
214 215 closing_widget.execute("exit")
215 216 self.tab_widget.removeTab(current_tab)
216 background(kernel_manager.stop_channels)
217 background(kernel_client.stop_channels)
217 218 elif reply == 0: # close Console
218 219 if not closing_widget._existing:
219 220 # Have kernel: don't quit, just close the tab
220 221 closing_widget.execute("exit True")
221 222 self.tab_widget.removeTab(current_tab)
222 background(kernel_manager.stop_channels)
223 background(kernel_client.stop_channels)
223 224 else:
224 225 reply = QtGui.QMessageBox.question(self, title,
225 226 "Are you sure you want to close this Console?"+
@@ -231,15 +232,16 b' class MainWindow(QtGui.QMainWindow):'
231 232 self.tab_widget.removeTab(current_tab)
232 233 elif keepkernel: #close console but leave kernel running (no prompt)
233 234 self.tab_widget.removeTab(current_tab)
234 background(kernel_manager.stop_channels)
235 background(kernel_client.stop_channels)
235 236 else: #close console and kernel (no prompt)
236 237 self.tab_widget.removeTab(current_tab)
237 if kernel_manager and kernel_manager.channels_running:
238 if kernel_client and kernel_client.channels_running:
238 239 for slave in slave_tabs:
239 background(slave.kernel_manager.stop_channels)
240 background(slave.kernel_client.stop_channels)
240 241 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
241 kernel_manager.shutdown_kernel()
242 background(kernel_manager.stop_channels)
242 if kernel_manager:
243 kernel_manager.shutdown_kernel()
244 background(kernel_client.stop_channels)
243 245
244 246 self.update_tab_bar_visibility()
245 247
@@ -284,7 +286,7 b' class MainWindow(QtGui.QMainWindow):'
284 286 #convert from/to int/richIpythonWidget if needed
285 287 if isinstance(tab, int):
286 288 tab = self.tab_widget.widget(tab)
287 km=tab.kernel_manager
289 km=tab.kernel_client
288 290
289 291 #build list of all widgets
290 292 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
@@ -292,7 +294,7 b' class MainWindow(QtGui.QMainWindow):'
292 294 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
293 295 # And should have a _may_close attribute
294 296 filtered_widget_list = [ widget for widget in widget_list if
295 widget.kernel_manager.connection_file == km.connection_file and
297 widget.kernel_client.connection_file == km.connection_file and
296 298 hasattr(widget,'_may_close') ]
297 299 # the master widget is the one that may close the kernel
298 300 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
@@ -315,14 +317,14 b' class MainWindow(QtGui.QMainWindow):'
315 317 #convert from/to int/richIpythonWidget if needed
316 318 if isinstance(tab, int):
317 319 tab = self.tab_widget.widget(tab)
318 km=tab.kernel_manager
320 km=tab.kernel_client
319 321
320 322 #build list of all widgets
321 323 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
322 324
323 325 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
324 326 filtered_widget_list = ( widget for widget in widget_list if
325 widget.kernel_manager.connection_file == km.connection_file)
327 widget.kernel_client.connection_file == km.connection_file)
326 328 # Get a list of all widget owning the same kernel and removed it from
327 329 # the previous cadidate. (better using sets ?)
328 330 master_widget_list = self.find_master_tab(tab, as_list=True)
@@ -20,11 +20,9 b' Authors:'
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 import json
24 23 import os
25 24 import signal
26 25 import sys
27 import uuid
28 26
29 27 # If run on Windows, install an exception hook which pops up a
30 28 # message box. Pythonw.exe hides the console, so without this
@@ -59,21 +57,17 b' from IPython.external.qt import QtCore, QtGui'
59 57 from IPython.config.application import boolean_flag, catch_config_error
60 58 from IPython.core.application import BaseIPythonApplication
61 59 from IPython.core.profiledir import ProfileDir
62 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
63 60 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
64 61 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
65 62 from IPython.frontend.qt.console import styles
66 63 from IPython.frontend.qt.console.mainwindow import MainWindow
67 from IPython.frontend.qt.kernelmanager import QtKernelManager
64 from IPython.frontend.qt.client import QtKernelClient
65 from IPython.frontend.qt.manager import QtKernelManager
68 66 from IPython.kernel import tunnel_to_kernel, find_connection_file
69 from IPython.utils.path import filefind
70 from IPython.utils.py3compat import str_to_bytes
71 67 from IPython.utils.traitlets import (
72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
68 Dict, List, Unicode, CBool, Any
73 69 )
74 from IPython.kernel.zmq.kernelapp import IPKernelApp
75 from IPython.kernel.zmq.session import Session, default_secure
76 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
70 from IPython.kernel.zmq.session import default_secure
77 71
78 72 from IPython.frontend.consoleapp import (
79 73 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
@@ -166,6 +160,7 b' class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):'
166 160 aliases = Dict(aliases)
167 161 frontend_flags = Any(qt_flags)
168 162 frontend_aliases = Any(qt_aliases)
163 kernel_client_class = QtKernelClient
169 164 kernel_manager_class = QtKernelManager
170 165
171 166 stylesheet = Unicode('', config=True,
@@ -196,16 +191,20 b' class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):'
196 191 kernel_manager = self.kernel_manager_class(
197 192 connection_file=self._new_connection_file(),
198 193 config=self.config,
194 autorestart=True,
199 195 )
200 196 # start the kernel
201 197 kwargs = dict()
202 198 kwargs['extra_arguments'] = self.kernel_argv
203 199 kernel_manager.start_kernel(**kwargs)
204 kernel_manager.start_channels()
200 kernel_manager.client_factory = self.kernel_client_class
201 kernel_client = kernel_manager.client()
202 kernel_client.start_channels(shell=True, iopub=True)
205 203 widget = self.widget_factory(config=self.config,
206 204 local_kernel=True)
207 205 self.init_colors(widget)
208 206 widget.kernel_manager = kernel_manager
207 widget.kernel_client = kernel_client
209 208 widget._existing = False
210 209 widget._may_close = True
211 210 widget._confirm_exit = self.confirm_exit
@@ -219,24 +218,28 b' class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):'
219 218 current_widget : IPythonWidget
220 219 The IPythonWidget whose kernel this frontend is to share
221 220 """
222 kernel_manager = self.kernel_manager_class(
223 connection_file=current_widget.kernel_manager.connection_file,
221 kernel_client = self.kernel_client_class(
222 connection_file=current_widget.kernel_client.connection_file,
224 223 config = self.config,
225 224 )
226 kernel_manager.load_connection_file()
227 kernel_manager.start_channels()
225 kernel_client.load_connection_file()
226 kernel_client.start_channels()
228 227 widget = self.widget_factory(config=self.config,
229 228 local_kernel=False)
230 229 self.init_colors(widget)
231 230 widget._existing = True
232 231 widget._may_close = False
233 232 widget._confirm_exit = False
234 widget.kernel_manager = kernel_manager
233 widget.kernel_client = kernel_client
234 widget.kernel_manager = current_widget.kernel_manager
235 235 return widget
236 236
237 def init_qt_app(self):
238 # separate from qt_elements, because it must run first
239 self.app = QtGui.QApplication([])
240
237 241 def init_qt_elements(self):
238 242 # Create the widget.
239 self.app = QtGui.QApplication([])
240 243
241 244 base_path = os.path.abspath(os.path.dirname(__file__))
242 245 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
@@ -256,6 +259,7 b' class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):'
256 259 self.widget._confirm_exit = self.confirm_exit
257 260
258 261 self.widget.kernel_manager = self.kernel_manager
262 self.widget.kernel_client = self.kernel_client
259 263 self.window = MainWindow(self.app,
260 264 confirm_exit=self.confirm_exit,
261 265 new_frontend_factory=self.new_frontend_master,
@@ -342,6 +346,7 b' class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):'
342 346
343 347 @catch_config_error
344 348 def initialize(self, argv=None):
349 self.init_qt_app()
345 350 super(IPythonQtConsoleApp, self).initialize(argv)
346 351 IPythonConsoleApp.initialize(self,argv)
347 352 self.init_qt_elements()
@@ -2,12 +2,17 b''
2 2 """
3 3
4 4 # Local imports.
5 from IPython.kernel.inprocess.kernelmanager import \
6 InProcessShellChannel, InProcessIOPubChannel, InProcessStdInChannel, \
7 InProcessHBChannel, InProcessKernelManager
5 from IPython.kernel.inprocess import (
6 InProcessShellChannel, InProcessIOPubChannel, InProcessStdInChannel,
7 InProcessHBChannel, InProcessKernelClient, InProcessKernelManager,
8 )
9
8 10 from IPython.utils.traitlets import Type
9 from base_kernelmanager import QtShellChannelMixin, QtIOPubChannelMixin, \
10 QtStdInChannelMixin, QtHBChannelMixin, QtKernelManagerMixin
11 from .kernel_mixins import (
12 QtShellChannelMixin, QtIOPubChannelMixin,
13 QtStdInChannelMixin, QtHBChannelMixin, QtKernelClientMixin,
14 QtKernelManagerMixin,
15 )
11 16
12 17
13 18 class QtInProcessShellChannel(QtShellChannelMixin, InProcessShellChannel):
@@ -22,8 +27,7 b' class QtInProcessStdInChannel(QtStdInChannelMixin, InProcessStdInChannel):'
22 27 class QtInProcessHBChannel(QtHBChannelMixin, InProcessHBChannel):
23 28 pass
24 29
25
26 class QtInProcessKernelManager(QtKernelManagerMixin, InProcessKernelManager):
30 class QtInProcessKernelClient(QtKernelClientMixin, InProcessKernelClient):
27 31 """ An in-process KernelManager with signals and slots.
28 32 """
29 33
@@ -31,3 +35,6 b' class QtInProcessKernelManager(QtKernelManagerMixin, InProcessKernelManager):'
31 35 shell_channel_class = Type(QtInProcessShellChannel)
32 36 stdin_channel_class = Type(QtInProcessStdInChannel)
33 37 hb_channel_class = Type(QtInProcessHBChannel)
38
39 class QtInProcessKernelManager(QtKernelManagerMixin, InProcessKernelManager):
40 client_class = __module__ + '.QtInProcessKernelClient'
@@ -54,20 +54,12 b' class QtShellChannelMixin(ChannelQObject):'
54 54 # Emitted when any message is received.
55 55 message_received = QtCore.Signal(object)
56 56
57 # Emitted when a reply has been received for the corresponding request
58 # type.
57 # Emitted when a reply has been received for the corresponding request type.
59 58 execute_reply = QtCore.Signal(object)
60 59 complete_reply = QtCore.Signal(object)
61 60 object_info_reply = QtCore.Signal(object)
62 61 history_reply = QtCore.Signal(object)
63 62
64 # Emitted when the first reply comes back.
65 first_reply = QtCore.Signal()
66
67 # Used by the first_reply signal logic to determine if a reply is the
68 # first.
69 _handlers_called = False
70
71 63 #---------------------------------------------------------------------------
72 64 # 'ShellChannel' interface
73 65 #---------------------------------------------------------------------------
@@ -84,19 +76,6 b' class QtShellChannelMixin(ChannelQObject):'
84 76 if signal:
85 77 signal.emit(msg)
86 78
87 if not self._handlers_called:
88 self.first_reply.emit()
89 self._handlers_called = True
90
91 #---------------------------------------------------------------------------
92 # 'QtShellChannelMixin' interface
93 #---------------------------------------------------------------------------
94
95 def reset_first_reply(self):
96 """ Reset the first_reply signal to fire again on the next reply.
97 """
98 self._handlers_called = False
99
100 79
101 80 class QtIOPubChannelMixin(ChannelQObject):
102 81
@@ -189,19 +168,31 b' class QtHBChannelMixin(ChannelQObject):'
189 168 self.kernel_died.emit(since_last_heartbeat)
190 169
191 170
171 class QtKernelRestarterMixin(HasTraits, SuperQObject):
172
173 __metaclass__ = MetaQObjectHasTraits
174 _timer = None
175
176
192 177 class QtKernelManagerMixin(HasTraits, SuperQObject):
193 """ A KernelManager that provides signals and slots.
178 """ A KernelClient that provides signals and slots.
194 179 """
195 180
196 181 __metaclass__ = MetaQObjectHasTraits
197 182
198 # Emitted when the kernel manager has started listening.
199 started_kernel = QtCore.Signal()
183 kernel_restarted = QtCore.Signal()
184
200 185
201 # Emitted when the kernel manager has started listening.
186 class QtKernelClientMixin(HasTraits, SuperQObject):
187 """ A KernelClient that provides signals and slots.
188 """
189
190 __metaclass__ = MetaQObjectHasTraits
191
192 # Emitted when the kernel client has started listening.
202 193 started_channels = QtCore.Signal()
203 194
204 # Emitted when the kernel manager has stopped listening.
195 # Emitted when the kernel client has stopped listening.
205 196 stopped_channels = QtCore.Signal()
206 197
207 198 # Use Qt-specific channel classes that emit signals.
@@ -211,50 +202,19 b' class QtKernelManagerMixin(HasTraits, SuperQObject):'
211 202 hb_channel_class = Type(QtHBChannelMixin)
212 203
213 204 #---------------------------------------------------------------------------
214 # 'KernelManager' interface
205 # 'KernelClient' interface
215 206 #---------------------------------------------------------------------------
216 207
217 #------ Kernel process management ------------------------------------------
218
219 def start_kernel(self, *args, **kw):
220 """ Reimplemented for proper heartbeat management.
221 """
222 if self._shell_channel is not None:
223 self._shell_channel.reset_first_reply()
224 super(QtKernelManagerMixin, self).start_kernel(*args, **kw)
225 self.started_kernel.emit()
226
227 208 #------ Channel management -------------------------------------------------
228 209
229 210 def start_channels(self, *args, **kw):
230 211 """ Reimplemented to emit signal.
231 212 """
232 super(QtKernelManagerMixin, self).start_channels(*args, **kw)
213 super(QtKernelClientMixin, self).start_channels(*args, **kw)
233 214 self.started_channels.emit()
234 215
235 216 def stop_channels(self):
236 217 """ Reimplemented to emit signal.
237 218 """
238 super(QtKernelManagerMixin, self).stop_channels()
219 super(QtKernelClientMixin, self).stop_channels()
239 220 self.stopped_channels.emit()
240
241 @property
242 def shell_channel(self):
243 """ Reimplemented for proper heartbeat management.
244 """
245 if self._shell_channel is None:
246 self._shell_channel = super(QtKernelManagerMixin,self).shell_channel
247 self._shell_channel.first_reply.connect(self._first_reply)
248 return self._shell_channel
249
250 #---------------------------------------------------------------------------
251 # Protected interface
252 #---------------------------------------------------------------------------
253
254 def _first_reply(self):
255 """ Unpauses the heartbeat channel when the first reply is received on
256 the execute channel. Note that this will *not* start the heartbeat
257 channel if it is not already running!
258 """
259 if self._hb_channel is not None:
260 self._hb_channel.unpause()
@@ -114,7 +114,10 b' class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):'
114 114 signal.signal(signal.SIGINT, self.handle_sigint)
115 115 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
116 116 display_banner=False, profile_dir=self.profile_dir,
117 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
117 ipython_dir=self.ipython_dir,
118 manager=self.kernel_manager,
119 client=self.kernel_client,
120 )
118 121
119 122 def init_gui_pylab(self):
120 123 # no-op, because we don't want to import matplotlib in the frontend.
@@ -122,7 +125,7 b' class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):'
122 125
123 126 def handle_sigint(self, *args):
124 127 if self.shell._executing:
125 if self.kernel_manager.has_kernel:
128 if self.kernel_manager:
126 129 # interrupt already gets passed to subprocess by signal handler.
127 130 # Only if we prevent that should we need to explicitly call
128 131 # interrupt_kernel, until which time, this would result in a
@@ -9,9 +9,9 b' class ZMQCompleter(object):'
9 9 state=0,1,2,... When state=0 it should compute ALL the completion matches,
10 10 and then return them for each value of state."""
11 11
12 def __init__(self, shell, km):
12 def __init__(self, shell, client):
13 13 self.shell = shell
14 self.km = km
14 self.client = client
15 15 self.matches = []
16 16
17 17 def complete_request(self,text):
@@ -20,10 +20,10 b' class ZMQCompleter(object):'
20 20
21 21 # send completion request to kernel
22 22 # Give the kernel up to 0.5s to respond
23 msg_id = self.km.shell_channel.complete(text=text, line=line,
23 msg_id = self.client.shell_channel.complete(text=text, line=line,
24 24 cursor_pos=cursor_pos)
25 25
26 msg = self.km.shell_channel.get_msg(timeout=0.5)
26 msg = self.client.shell_channel.get_msg(timeout=0.5)
27 27 if msg['parent_header']['msg_id'] == msg_id:
28 28 return msg["content"]["matches"]
29 29 return []
@@ -1,12 +1,9 b''
1 1 # -*- coding: utf-8 -*-
2 """Frontend of ipython working with python-zmq
2 """terminal client to the IPython kernel
3 3
4 Ipython's frontend, is a ipython interface that send request to kernel and proccess the kernel's outputs.
5
6 For more details, see the ipython-zmq design
7 4 """
8 5 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
6 # Copyright (C) 2013 The IPython Development Team
10 7 #
11 8 # Distributed under the terms of the BSD License. The full license is in
12 9 # the file COPYING, distributed as part of this software.
@@ -37,7 +34,7 b' from IPython.core.alias import AliasManager, AliasError'
37 34 from IPython.core import page
38 35 from IPython.utils.warn import warn, error, fatal
39 36 from IPython.utils import io
40 from IPython.utils.traitlets import List, Enum, Any
37 from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode
41 38 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
42 39
43 40 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
@@ -105,11 +102,12 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
105 102 """
106 103 )
107 104
108 def __init__(self, *args, **kwargs):
109 self.km = kwargs.pop('kernel_manager')
110 self.session_id = self.km.session.session
111 super(ZMQTerminalInteractiveShell, self).__init__(*args, **kwargs)
112
105 manager = Instance('IPython.kernel.KernelManager')
106 client = Instance('IPython.kernel.KernelClient')
107 def _client_changed(self, name, old, new):
108 self.session_id = new.session.session
109 session_id = Unicode()
110
113 111 def init_completer(self):
114 112 """Initialize the completion machinery.
115 113
@@ -121,7 +119,7 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
121 119 from IPython.core.completerlib import (module_completer,
122 120 magic_run_completer, cd_completer)
123 121
124 self.Completer = ZMQCompleter(self, self.km)
122 self.Completer = ZMQCompleter(self, self.client)
125 123
126 124
127 125 self.set_hook('complete_command', module_completer, str_key = 'import')
@@ -156,18 +154,18 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
156 154
157 155 self._executing = True
158 156 # flush stale replies, which could have been ignored, due to missed heartbeats
159 while self.km.shell_channel.msg_ready():
160 self.km.shell_channel.get_msg()
157 while self.client.shell_channel.msg_ready():
158 self.client.shell_channel.get_msg()
161 159 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
162 msg_id = self.km.shell_channel.execute(cell, not store_history)
163 while not self.km.shell_channel.msg_ready() and self.km.is_alive:
160 msg_id = self.client.shell_channel.execute(cell, not store_history)
161 while not self.client.shell_channel.msg_ready() and self.client.is_alive():
164 162 try:
165 163 self.handle_stdin_request(timeout=0.05)
166 164 except Empty:
167 165 # display intermediate print statements, etc.
168 166 self.handle_iopub()
169 167 pass
170 if self.km.shell_channel.msg_ready():
168 if self.client.shell_channel.msg_ready():
171 169 self.handle_execute_reply(msg_id)
172 170 self._executing = False
173 171
@@ -176,7 +174,7 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
176 174 #-----------------
177 175
178 176 def handle_execute_reply(self, msg_id):
179 msg = self.km.shell_channel.get_msg()
177 msg = self.client.shell_channel.get_msg()
180 178 if msg["parent_header"].get("msg_id", None) == msg_id:
181 179
182 180 self.handle_iopub()
@@ -211,8 +209,8 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
211 209 sub_msg: message receive from kernel in the sub socket channel
212 210 capture by kernel manager.
213 211 """
214 while self.km.iopub_channel.msg_ready():
215 sub_msg = self.km.iopub_channel.get_msg()
212 while self.client.iopub_channel.msg_ready():
213 sub_msg = self.client.iopub_channel.get_msg()
216 214 msg_type = sub_msg['header']['msg_type']
217 215 parent = sub_msg["parent_header"]
218 216 if (not parent) or self.session_id == parent['session']:
@@ -298,7 +296,7 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
298 296 def handle_stdin_request(self, timeout=0.1):
299 297 """ Method to capture raw_input
300 298 """
301 msg_rep = self.km.stdin_channel.get_msg(timeout=timeout)
299 msg_rep = self.client.stdin_channel.get_msg(timeout=timeout)
302 300 # in case any iopub came while we were waiting:
303 301 self.handle_iopub()
304 302 if self.session_id == msg_rep["parent_header"].get("session"):
@@ -325,8 +323,8 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
325 323
326 324 # only send stdin reply if there *was not* another request
327 325 # or execution finished while we were reading.
328 if not (self.km.stdin_channel.msg_ready() or self.km.shell_channel.msg_ready()):
329 self.km.stdin_channel.input(raw_data)
326 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
327 self.client.stdin_channel.input(raw_data)
330 328
331 329 def mainloop(self, display_banner=False):
332 330 while True:
@@ -344,10 +342,10 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
344 342 def wait_for_kernel(self, timeout=None):
345 343 """method to wait for a kernel to be ready"""
346 344 tic = time.time()
347 self.km.hb_channel.unpause()
345 self.client.hb_channel.unpause()
348 346 while True:
349 347 self.run_cell('1', False)
350 if self.km.hb_channel.is_beating():
348 if self.client.hb_channel.is_beating():
351 349 # heart failure was not the reason this returned
352 350 break
353 351 else:
@@ -389,13 +387,14 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
389 387 # ask_exit callback.
390 388
391 389 while not self.exit_now:
392 if not self.km.is_alive:
390 if not self.client.is_alive():
393 391 # kernel died, prompt for action or exit
394 action = "restart" if self.km.has_kernel else "wait for restart"
392
393 action = "restart" if self.manager else "wait for restart"
395 394 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
396 395 if ans:
397 if self.km.has_kernel:
398 self.km.restart_kernel(True)
396 if self.manager:
397 self.manager.restart_kernel(True)
399 398 self.wait_for_kernel(3)
400 399 else:
401 400 self.exit_now = True
@@ -10,7 +10,7 b' import sys'
10 10 import unittest
11 11 import base64
12 12
13 from IPython.kernel.kernelmanager import KernelManager
13 from IPython.kernel import KernelClient
14 14 from IPython.frontend.terminal.console.interactiveshell \
15 15 import ZMQTerminalInteractiveShell
16 16 from IPython.utils.tempdir import TemporaryDirectory
@@ -26,8 +26,8 b' SCRIPT_PATH = os.path.join('
26 26 class ZMQTerminalInteractiveShellTestCase(unittest.TestCase):
27 27
28 28 def setUp(self):
29 km = KernelManager()
30 self.shell = ZMQTerminalInteractiveShell(kernel_manager=km)
29 client = KernelClient()
30 self.shell = ZMQTerminalInteractiveShell(kernel_client=client)
31 31 self.raw = b'dummy data'
32 32 self.mime = 'image/png'
33 33 self.data = {self.mime: base64.encodestring(self.raw).decode('ascii')}
@@ -368,11 +368,13 b' class TerminalInteractiveShell(InteractiveShell):'
368 368
369 369 def __init__(self, config=None, ipython_dir=None, profile_dir=None,
370 370 user_ns=None, user_module=None, custom_exceptions=((),None),
371 usage=None, banner1=None, banner2=None, display_banner=None):
371 usage=None, banner1=None, banner2=None, display_banner=None,
372 **kwargs):
372 373
373 374 super(TerminalInteractiveShell, self).__init__(
374 375 config=config, ipython_dir=ipython_dir, profile_dir=profile_dir, user_ns=user_ns,
375 user_module=user_module, custom_exceptions=custom_exceptions
376 user_module=user_module, custom_exceptions=custom_exceptions,
377 **kwargs
376 378 )
377 379 # use os.system instead of utils.process.system by default,
378 380 # because piped system doesn't make sense in the Terminal:
@@ -5,6 +5,7 b' from . import zmq'
5 5
6 6 from .connect import *
7 7 from .launcher import *
8 from .kernelmanager import KernelManager
9 from .blockingkernelmanager import BlockingKernelManager
8 from .client import KernelClient
9 from .manager import KernelManager
10 from .blocking import BlockingKernelClient
10 11 from .multikernelmanager import MultiKernelManager
@@ -1,9 +1,9 b''
1 """ Implements a fully blocking kernel manager.
1 """Blocking channels
2 2
3 3 Useful for test suites and blocking terminal interfaces.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2010-2012 The IPython Development Team
6 # Copyright (C) 2013 The IPython Development Team
7 7 #
8 8 # Distributed under the terms of the BSD License. The full license is in
9 9 # the file COPYING.txt, distributed as part of this software.
@@ -15,8 +15,7 b' Useful for test suites and blocking terminal interfaces.'
15 15
16 16 import Queue
17 17
18 from IPython.utils.traitlets import Type
19 from .kernelmanager import KernelManager, IOPubChannel, HBChannel, \
18 from IPython.kernel.channels import IOPubChannel, HBChannel, \
20 19 ShellChannel, StdInChannel
21 20
22 21 #-----------------------------------------------------------------------------
@@ -25,14 +24,14 b' from .kernelmanager import KernelManager, IOPubChannel, HBChannel, \\'
25 24
26 25
27 26 class BlockingChannelMixin(object):
28
27
29 28 def __init__(self, *args, **kwds):
30 29 super(BlockingChannelMixin, self).__init__(*args, **kwds)
31 30 self._in_queue = Queue.Queue()
32
31
33 32 def call_handlers(self, msg):
34 33 self._in_queue.put(msg)
35
34
36 35 def get_msg(self, block=True, timeout=None):
37 36 """ Gets a message if there is one that is ready. """
38 37 if timeout is None:
@@ -40,7 +39,7 b' class BlockingChannelMixin(object):'
40 39 # behavior, so wait for a week instead
41 40 timeout = 604800
42 41 return self._in_queue.get(block, timeout)
43
42
44 43 def get_msgs(self):
45 44 """ Get all messages that are currently ready. """
46 45 msgs = []
@@ -50,7 +49,7 b' class BlockingChannelMixin(object):'
50 49 except Queue.Empty:
51 50 break
52 51 return msgs
53
52
54 53 def msg_ready(self):
55 54 """ Is there a message that has been received? """
56 55 return not self._in_queue.empty()
@@ -69,7 +68,7 b' class BlockingStdInChannel(BlockingChannelMixin, StdInChannel):'
69 68
70 69
71 70 class BlockingHBChannel(HBChannel):
72
71
73 72 # This kernel needs quicker monitoring, shorten to 1 sec.
74 73 # less than 0.5s is unreliable, and will get occasional
75 74 # false reports of missed beats.
@@ -78,13 +77,3 b' class BlockingHBChannel(HBChannel):'
78 77 def call_handlers(self, since_last_heartbeat):
79 78 """ Pause beating on missed heartbeat. """
80 79 pass
81
82
83 class BlockingKernelManager(KernelManager):
84
85 # The classes to use for the various channels.
86 shell_channel_class = Type(BlockingShellChannel)
87 iopub_channel_class = Type(BlockingIOPubChannel)
88 stdin_channel_class = Type(BlockingStdInChannel)
89 hb_channel_class = Type(BlockingHBChannel)
90
This diff has been collapsed as it changes many lines, (538 lines changed) Show them Hide them
@@ -1,11 +1,8 b''
1 """Base classes to manage the interaction with a running kernel.
2
3 TODO
4 * Create logger to handle debugging and console messages.
1 """Base classes to manage a Client's interaction with a running kernel
5 2 """
6 3
7 4 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2011 The IPython Development Team
5 # Copyright (C) 2013 The IPython Development Team
9 6 #
10 7 # Distributed under the terms of the BSD License. The full license is in
11 8 # the file COPYING, distributed as part of this software.
@@ -20,15 +17,9 b' from __future__ import absolute_import'
20 17 # Standard library imports
21 18 import atexit
22 19 import errno
23 import json
24 from subprocess import Popen
25 import os
26 import signal
27 import sys
28 20 from threading import Thread
29 21 import time
30 22
31 # System library imports
32 23 import zmq
33 24 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
34 25 # during garbage collection of threads at exit:
@@ -36,25 +27,11 b' from zmq import ZMQError'
36 27 from zmq.eventloop import ioloop, zmqstream
37 28
38 29 # Local imports
39 from IPython.config.configurable import Configurable
40 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
41 from IPython.utils.traitlets import (
42 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
43 )
44 from IPython.utils.py3compat import str_to_bytes
45 from IPython.kernel import (
46 write_connection_file,
47 make_ipkernel_cmd,
48 launch_kernel,
49 )
50 from .zmq.session import Session
51 from .kernelmanagerabc import (
30 from .channelsabc import (
52 31 ShellChannelABC, IOPubChannelABC,
53 32 HBChannelABC, StdInChannelABC,
54 KernelManagerABC
55 33 )
56 34
57
58 35 #-----------------------------------------------------------------------------
59 36 # Constants and exceptions
60 37 #-----------------------------------------------------------------------------
@@ -104,6 +81,7 b' class ZMQSocketChannel(Thread):'
104 81 stream = None
105 82 _address = None
106 83 _exiting = False
84 proxy_methods = []
107 85
108 86 def __init__(self, context, session, address):
109 87 """Create a channel.
@@ -129,7 +107,7 b' class ZMQSocketChannel(Thread):'
129 107 address = "tcp://%s:%i" % address
130 108 self._address = address
131 109 atexit.register(self._notice_exit)
132
110
133 111 def _notice_exit(self):
134 112 self._exiting = True
135 113
@@ -170,11 +148,11 b' class ZMQSocketChannel(Thread):'
170 148
171 149 def _queue_send(self, msg):
172 150 """Queue a message to be sent from the IOLoop's thread.
173
151
174 152 Parameters
175 153 ----------
176 154 msg : message to send
177
155
178 156 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
179 157 thread control of the action.
180 158 """
@@ -189,7 +167,7 b' class ZMQSocketChannel(Thread):'
189 167 """
190 168 ident,smsg = self.session.feed_identities(msg)
191 169 self.call_handlers(self.session.unserialize(smsg))
192
170
193 171
194 172
195 173 class ShellChannel(ZMQSocketChannel):
@@ -198,6 +176,14 b' class ShellChannel(ZMQSocketChannel):'
198 176 command_queue = None
199 177 # flag for whether execute requests should be allowed to call raw_input:
200 178 allow_stdin = True
179 proxy_methods = [
180 'execute',
181 'complete',
182 'object_info',
183 'history',
184 'kernel_info',
185 'shutdown',
186 ]
201 187
202 188 def __init__(self, context, session, address):
203 189 super(ShellChannel, self).__init__(context, session, address)
@@ -226,7 +212,7 b' class ShellChannel(ZMQSocketChannel):'
226 212
227 213 Subclasses should override this method to handle incoming messages.
228 214 It is important to remember that this method is called in the thread
229 so that some logic must be done to ensure that the application leve
215 so that some logic must be done to ensure that the application level
230 216 handlers are called in the application thread.
231 217 """
232 218 raise NotImplementedError('call_handlers must be defined in a subclass.')
@@ -261,7 +247,7 b' class ShellChannel(ZMQSocketChannel):'
261 247 allow_stdin : bool, optional (default self.allow_stdin)
262 248 Flag for whether the kernel can send stdin requests to frontends.
263 249
264 Some frontends (e.g. the Notebook) do not support stdin requests.
250 Some frontends (e.g. the Notebook) do not support stdin requests.
265 251 If raw_input is called from code executed from such a frontend, a
266 252 StdinNotImplementedError will be raised.
267 253
@@ -275,8 +261,8 b' class ShellChannel(ZMQSocketChannel):'
275 261 user_expressions = {}
276 262 if allow_stdin is None:
277 263 allow_stdin = self.allow_stdin
278
279
264
265
280 266 # Don't waste network traffic if inputs are invalid
281 267 if not isinstance(code, basestring):
282 268 raise ValueError('code %r must be a string' % code)
@@ -474,6 +460,7 b' class StdInChannel(ZMQSocketChannel):'
474 460 """The stdin channel to handle raw_input requests that the kernel makes."""
475 461
476 462 msg_queue = None
463 proxy_methods = ['input']
477 464
478 465 def __init__(self, context, session, address):
479 466 super(StdInChannel, self).__init__(context, session, address)
@@ -543,17 +530,17 b' class HBChannel(ZMQSocketChannel):'
543 530 self.socket = self.context.socket(zmq.REQ)
544 531 self.socket.setsockopt(zmq.LINGER, 0)
545 532 self.socket.connect(self.address)
546
533
547 534 self.poller.register(self.socket, zmq.POLLIN)
548
535
549 536 def _poll(self, start_time):
550 537 """poll for heartbeat replies until we reach self.time_to_dead.
551
538
552 539 Ignores interrupts, and returns the result of poll(), which
553 540 will be an empty list if no messages arrived before the timeout,
554 541 or the event tuple if there is a message to receive.
555 542 """
556
543
557 544 until_dead = self.time_to_dead - (time.time() - start_time)
558 545 # ensure poll at least once
559 546 until_dead = max(until_dead, 1e-3)
@@ -584,13 +571,13 b' class HBChannel(ZMQSocketChannel):'
584 571 self._create_socket()
585 572 self._running = True
586 573 self._beating = True
587
574
588 575 while self._running:
589 576 if self._pause:
590 577 # just sleep, and skip the rest of the loop
591 578 time.sleep(self.time_to_dead)
592 579 continue
593
580
594 581 since_last_heartbeat = 0.0
595 582 # io.rprint('Ping from HB channel') # dbg
596 583 # no need to catch EFSM here, because the previous event was
@@ -651,474 +638,7 b' class HBChannel(ZMQSocketChannel):'
651 638 raise NotImplementedError('call_handlers must be defined in a subclass.')
652 639
653 640
654 #-----------------------------------------------------------------------------
655 # Main kernel manager class
656 #-----------------------------------------------------------------------------
657
658 class KernelManager(Configurable):
659 """Manages a single kernel on this host along with its channels.
660
661 There are four channels associated with each kernel:
662
663 * shell: for request/reply calls to the kernel.
664 * iopub: for the kernel to publish results to frontends.
665 * hb: for monitoring the kernel's heartbeat.
666 * stdin: for frontends to reply to raw_input calls in the kernel.
667
668 The usage of the channels that this class manages is optional. It is
669 entirely possible to connect to the kernels directly using ZeroMQ
670 sockets. These channels are useful primarily for talking to a kernel
671 whose :class:`KernelManager` is in the same process.
672
673 This version manages kernels started using Popen.
674 """
675 # The PyZMQ Context to use for communication with the kernel.
676 context = Instance(zmq.Context)
677 def _context_default(self):
678 return zmq.Context.instance()
679
680 # The Session to use for communication with the kernel.
681 session = Instance(Session)
682 def _session_default(self):
683 return Session(config=self.config)
684
685 # The kernel process with which the KernelManager is communicating.
686 # generally a Popen instance
687 kernel = Any()
688
689 kernel_cmd = List(Unicode, config=True,
690 help="""The Popen Command to launch the kernel.
691 Override this if you have a custom
692 """
693 )
694 def _kernel_cmd_changed(self, name, old, new):
695 self.ipython_kernel = False
696
697 ipython_kernel = Bool(True)
698
699
700 # The addresses for the communication channels.
701 connection_file = Unicode('')
702
703 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
704
705 ip = Unicode(LOCALHOST, config=True,
706 help="""Set the kernel\'s IP address [default localhost].
707 If the IP address is something other than localhost, then
708 Consoles on other machines will be able to connect
709 to the Kernel, so be careful!"""
710 )
711 def _ip_default(self):
712 if self.transport == 'ipc':
713 if self.connection_file:
714 return os.path.splitext(self.connection_file)[0] + '-ipc'
715 else:
716 return 'kernel-ipc'
717 else:
718 return LOCALHOST
719 def _ip_changed(self, name, old, new):
720 if new == '*':
721 self.ip = '0.0.0.0'
722 shell_port = Integer(0)
723 iopub_port = Integer(0)
724 stdin_port = Integer(0)
725 hb_port = Integer(0)
726
727 # The classes to use for the various channels.
728 shell_channel_class = Type(ShellChannel)
729 iopub_channel_class = Type(IOPubChannel)
730 stdin_channel_class = Type(StdInChannel)
731 hb_channel_class = Type(HBChannel)
732
733 # Protected traits.
734 _launch_args = Any
735 _shell_channel = Any
736 _iopub_channel = Any
737 _stdin_channel = Any
738 _hb_channel = Any
739 _connection_file_written=Bool(False)
740
741 def __del__(self):
742 self.cleanup_connection_file()
743
744 #--------------------------------------------------------------------------
745 # Channel management methods:
746 #--------------------------------------------------------------------------
747
748 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
749 """Starts the channels for this kernel.
750
751 This will create the channels if they do not exist and then start
752 them (their activity runs in a thread). If port numbers of 0 are
753 being used (random ports) then you must first call
754 :method:`start_kernel`. If the channels have been stopped and you
755 call this, :class:`RuntimeError` will be raised.
756 """
757 if shell:
758 self.shell_channel.start()
759 if iopub:
760 self.iopub_channel.start()
761 if stdin:
762 self.stdin_channel.start()
763 self.shell_channel.allow_stdin = True
764 else:
765 self.shell_channel.allow_stdin = False
766 if hb:
767 self.hb_channel.start()
768
769 def stop_channels(self):
770 """Stops all the running channels for this kernel.
771
772 This stops their event loops and joins their threads.
773 """
774 if self.shell_channel.is_alive():
775 self.shell_channel.stop()
776 if self.iopub_channel.is_alive():
777 self.iopub_channel.stop()
778 if self.stdin_channel.is_alive():
779 self.stdin_channel.stop()
780 if self.hb_channel.is_alive():
781 self.hb_channel.stop()
782
783 @property
784 def channels_running(self):
785 """Are any of the channels created and running?"""
786 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
787 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
788
789 def _make_url(self, port):
790 """Make a zmq url with a port.
791
792 There are two cases that this handles:
793
794 * tcp: tcp://ip:port
795 * ipc: ipc://ip-port
796 """
797 if self.transport == 'tcp':
798 return "tcp://%s:%i" % (self.ip, port)
799 else:
800 return "%s://%s-%s" % (self.transport, self.ip, port)
801
802 @property
803 def shell_channel(self):
804 """Get the shell channel object for this kernel."""
805 if self._shell_channel is None:
806 self._shell_channel = self.shell_channel_class(
807 self.context, self.session, self._make_url(self.shell_port)
808 )
809 return self._shell_channel
810
811 @property
812 def iopub_channel(self):
813 """Get the iopub channel object for this kernel."""
814 if self._iopub_channel is None:
815 self._iopub_channel = self.iopub_channel_class(
816 self.context, self.session, self._make_url(self.iopub_port)
817 )
818 return self._iopub_channel
819
820 @property
821 def stdin_channel(self):
822 """Get the stdin channel object for this kernel."""
823 if self._stdin_channel is None:
824 self._stdin_channel = self.stdin_channel_class(
825 self.context, self.session, self._make_url(self.stdin_port)
826 )
827 return self._stdin_channel
828
829 @property
830 def hb_channel(self):
831 """Get the hb channel object for this kernel."""
832 if self._hb_channel is None:
833 self._hb_channel = self.hb_channel_class(
834 self.context, self.session, self._make_url(self.hb_port)
835 )
836 return self._hb_channel
837
838 #--------------------------------------------------------------------------
839 # Connection and ipc file management
840 #--------------------------------------------------------------------------
841
842 def cleanup_connection_file(self):
843 """Cleanup connection file *if we wrote it*
844
845 Will not raise if the connection file was already removed somehow.
846 """
847 if self._connection_file_written:
848 # cleanup connection files on full shutdown of kernel we started
849 self._connection_file_written = False
850 try:
851 os.remove(self.connection_file)
852 except (IOError, OSError, AttributeError):
853 pass
854
855 def cleanup_ipc_files(self):
856 """Cleanup ipc files if we wrote them."""
857 if self.transport != 'ipc':
858 return
859 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
860 ipcfile = "%s-%i" % (self.ip, port)
861 try:
862 os.remove(ipcfile)
863 except (IOError, OSError):
864 pass
865
866 def load_connection_file(self):
867 """Load connection info from JSON dict in self.connection_file."""
868 with open(self.connection_file) as f:
869 cfg = json.loads(f.read())
870
871 from pprint import pprint
872 pprint(cfg)
873 self.transport = cfg.get('transport', 'tcp')
874 self.ip = cfg['ip']
875 self.shell_port = cfg['shell_port']
876 self.stdin_port = cfg['stdin_port']
877 self.iopub_port = cfg['iopub_port']
878 self.hb_port = cfg['hb_port']
879 self.session.key = str_to_bytes(cfg['key'])
880
881 def write_connection_file(self):
882 """Write connection info to JSON dict in self.connection_file."""
883 if self._connection_file_written:
884 return
885 self.connection_file,cfg = write_connection_file(self.connection_file,
886 transport=self.transport, ip=self.ip, key=self.session.key,
887 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
888 shell_port=self.shell_port, hb_port=self.hb_port)
889 # write_connection_file also sets default ports:
890 self.shell_port = cfg['shell_port']
891 self.stdin_port = cfg['stdin_port']
892 self.iopub_port = cfg['iopub_port']
893 self.hb_port = cfg['hb_port']
894
895 self._connection_file_written = True
896
897 #--------------------------------------------------------------------------
898 # Kernel management
899 #--------------------------------------------------------------------------
900
901 def format_kernel_cmd(self, **kw):
902 """format templated args (e.g. {connection_file})"""
903 if self.kernel_cmd:
904 cmd = self.kernel_cmd
905 else:
906 cmd = make_ipkernel_cmd(
907 'from IPython.kernel.zmq.kernelapp import main; main()',
908 **kw
909 )
910 ns = dict(connection_file=self.connection_file)
911 ns.update(self._launch_args)
912 return [ c.format(**ns) for c in cmd ]
913
914 def _launch_kernel(self, kernel_cmd, **kw):
915 """actually launch the kernel
916
917 override in a subclass to launch kernel subprocesses differently
918 """
919 return launch_kernel(kernel_cmd, **kw)
920
921 def start_kernel(self, **kw):
922 """Starts a kernel on this host in a separate process.
923
924 If random ports (port=0) are being used, this method must be called
925 before the channels are created.
926
927 Parameters:
928 -----------
929 **kw : optional
930 keyword arguments that are passed down to build the kernel_cmd
931 and launching the kernel (e.g. Popen kwargs).
932 """
933 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
934 raise RuntimeError("Can only launch a kernel on a local interface. "
935 "Make sure that the '*_address' attributes are "
936 "configured properly. "
937 "Currently valid addresses are: %s"%LOCAL_IPS
938 )
939
940 # write connection file / get default ports
941 self.write_connection_file()
942
943 # save kwargs for use in restart
944 self._launch_args = kw.copy()
945 # build the Popen cmd
946 kernel_cmd = self.format_kernel_cmd(**kw)
947 # launch the kernel subprocess
948 self.kernel = self._launch_kernel(kernel_cmd,
949 ipython_kernel=self.ipython_kernel,
950 **kw)
951
952 def shutdown_kernel(self, now=False, restart=False):
953 """Attempts to the stop the kernel process cleanly.
954
955 This attempts to shutdown the kernels cleanly by:
956
957 1. Sending it a shutdown message over the shell channel.
958 2. If that fails, the kernel is shutdown forcibly by sending it
959 a signal.
960
961 Parameters:
962 -----------
963 now : bool
964 Should the kernel be forcible killed *now*. This skips the
965 first, nice shutdown attempt.
966 restart: bool
967 Will this kernel be restarted after it is shutdown. When this
968 is True, connection files will not be cleaned up.
969 """
970 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
971 if sys.platform == 'win32':
972 self._kill_kernel()
973 return
974
975 # Pause the heart beat channel if it exists.
976 if self._hb_channel is not None:
977 self._hb_channel.pause()
978
979 if now:
980 if self.has_kernel:
981 self._kill_kernel()
982 else:
983 # Don't send any additional kernel kill messages immediately, to give
984 # the kernel a chance to properly execute shutdown actions. Wait for at
985 # most 1s, checking every 0.1s.
986 self.shell_channel.shutdown(restart=restart)
987 for i in range(10):
988 if self.is_alive:
989 time.sleep(0.1)
990 else:
991 break
992 else:
993 # OK, we've waited long enough.
994 if self.has_kernel:
995 self._kill_kernel()
996
997 if not restart:
998 self.cleanup_connection_file()
999 self.cleanup_ipc_files()
1000 else:
1001 self.cleanup_ipc_files()
1002
1003 def restart_kernel(self, now=False, **kw):
1004 """Restarts a kernel with the arguments that were used to launch it.
1005
1006 If the old kernel was launched with random ports, the same ports will be
1007 used for the new kernel. The same connection file is used again.
1008
1009 Parameters
1010 ----------
1011 now : bool, optional
1012 If True, the kernel is forcefully restarted *immediately*, without
1013 having a chance to do any cleanup action. Otherwise the kernel is
1014 given 1s to clean up before a forceful restart is issued.
1015
1016 In all cases the kernel is restarted, the only difference is whether
1017 it is given a chance to perform a clean shutdown or not.
1018
1019 **kw : optional
1020 Any options specified here will overwrite those used to launch the
1021 kernel.
1022 """
1023 if self._launch_args is None:
1024 raise RuntimeError("Cannot restart the kernel. "
1025 "No previous call to 'start_kernel'.")
1026 else:
1027 # Stop currently running kernel.
1028 self.shutdown_kernel(now=now, restart=True)
1029
1030 # Start new kernel.
1031 self._launch_args.update(kw)
1032 self.start_kernel(**self._launch_args)
1033
1034 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1035 # unless there is some delay here.
1036 if sys.platform == 'win32':
1037 time.sleep(0.2)
1038
1039 @property
1040 def has_kernel(self):
1041 """Has a kernel been started that we are managing."""
1042 return self.kernel is not None
1043
1044 def _kill_kernel(self):
1045 """Kill the running kernel.
1046
1047 This is a private method, callers should use shutdown_kernel(now=True).
1048 """
1049 if self.has_kernel:
1050 # Pause the heart beat channel if it exists.
1051 if self._hb_channel is not None:
1052 self._hb_channel.pause()
1053
1054 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1055 # TerminateProcess() on Win32).
1056 try:
1057 self.kernel.kill()
1058 except OSError as e:
1059 # In Windows, we will get an Access Denied error if the process
1060 # has already terminated. Ignore it.
1061 if sys.platform == 'win32':
1062 if e.winerror != 5:
1063 raise
1064 # On Unix, we may get an ESRCH error if the process has already
1065 # terminated. Ignore it.
1066 else:
1067 from errno import ESRCH
1068 if e.errno != ESRCH:
1069 raise
1070
1071 # Block until the kernel terminates.
1072 self.kernel.wait()
1073 self.kernel = None
1074 else:
1075 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1076
1077 def interrupt_kernel(self):
1078 """Interrupts the kernel by sending it a signal.
1079
1080 Unlike ``signal_kernel``, this operation is well supported on all
1081 platforms.
1082 """
1083 if self.has_kernel:
1084 if sys.platform == 'win32':
1085 from .zmq.parentpoller import ParentPollerWindows as Poller
1086 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1087 else:
1088 self.kernel.send_signal(signal.SIGINT)
1089 else:
1090 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1091
1092 def signal_kernel(self, signum):
1093 """Sends a signal to the kernel.
1094
1095 Note that since only SIGTERM is supported on Windows, this function is
1096 only useful on Unix systems.
1097 """
1098 if self.has_kernel:
1099 self.kernel.send_signal(signum)
1100 else:
1101 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1102
1103 @property
1104 def is_alive(self):
1105 """Is the kernel process still running?"""
1106 if self.has_kernel:
1107 if self.kernel.poll() is None:
1108 return True
1109 else:
1110 return False
1111 elif self._hb_channel is not None:
1112 # We didn't start the kernel with this KernelManager so we
1113 # use the heartbeat.
1114 return self._hb_channel.is_beating()
1115 else:
1116 # no heartbeat and not local, we can't tell if it's running,
1117 # so naively return True
1118 return True
1119
1120
1121 #-----------------------------------------------------------------------------
641 #---------------------------------------------------------------------#-----------------------------------------------------------------------------
1122 642 # ABC Registration
1123 643 #-----------------------------------------------------------------------------
1124 644
@@ -1126,5 +646,3 b' ShellChannelABC.register(ShellChannel)'
1126 646 IOPubChannelABC.register(IOPubChannel)
1127 647 HBChannelABC.register(HBChannel)
1128 648 StdInChannelABC.register(StdInChannel)
1129 KernelManagerABC.register(KernelManager)
1130
@@ -17,6 +17,8 b' Authors:'
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 from __future__ import absolute_import
21
20 22 import glob
21 23 import json
22 24 import os
@@ -26,14 +28,21 b' from getpass import getpass'
26 28 from subprocess import Popen, PIPE
27 29 import tempfile
28 30
31 import zmq
32
29 33 # external imports
30 34 from IPython.external.ssh import tunnel
31 35
32 36 # IPython imports
37 # from IPython.config import Configurable
33 38 from IPython.core.profiledir import ProfileDir
34 39 from IPython.utils.localinterfaces import LOCALHOST
35 40 from IPython.utils.path import filefind, get_ipython_dir
36 41 from IPython.utils.py3compat import str_to_bytes, bytes_to_str
42 from IPython.utils.traitlets import (
43 Bool, Integer, Unicode, CaselessStrEnum,
44 HasTraits,
45 )
37 46
38 47
39 48 #-----------------------------------------------------------------------------
@@ -41,7 +50,7 b' from IPython.utils.py3compat import str_to_bytes, bytes_to_str'
41 50 #-----------------------------------------------------------------------------
42 51
43 52 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
44 ip=LOCALHOST, key=b'', transport='tcp'):
53 control_port=0, ip=LOCALHOST, key=b'', transport='tcp'):
45 54 """Generates a JSON config file, including the selection of random ports.
46 55
47 56 Parameters
@@ -51,16 +60,19 b' def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, '
51 60 The path to the file to write
52 61
53 62 shell_port : int, optional
54 The port to use for ROUTER channel.
63 The port to use for ROUTER (shell) channel.
55 64
56 65 iopub_port : int, optional
57 66 The port to use for the SUB channel.
58 67
59 68 stdin_port : int, optional
60 The port to use for the REQ (raw input) channel.
69 The port to use for the ROUTER (raw input) channel.
70
71 control_port : int, optional
72 The port to use for the ROUTER (control) channel.
61 73
62 74 hb_port : int, optional
63 The port to use for the hearbeat REP channel.
75 The port to use for the heartbeat REP channel.
64 76
65 77 ip : str, optional
66 78 The ip address the kernel will bind to.
@@ -76,8 +88,11 b' def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, '
76 88 # Find open ports as necessary.
77 89
78 90 ports = []
79 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
80 int(stdin_port <= 0) + int(hb_port <= 0)
91 ports_needed = int(shell_port <= 0) + \
92 int(iopub_port <= 0) + \
93 int(stdin_port <= 0) + \
94 int(control_port <= 0) + \
95 int(hb_port <= 0)
81 96 if transport == 'tcp':
82 97 for i in range(ports_needed):
83 98 sock = socket.socket()
@@ -100,12 +115,15 b' def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, '
100 115 iopub_port = ports.pop(0)
101 116 if stdin_port <= 0:
102 117 stdin_port = ports.pop(0)
118 if control_port <= 0:
119 control_port = ports.pop(0)
103 120 if hb_port <= 0:
104 121 hb_port = ports.pop(0)
105 122
106 123 cfg = dict( shell_port=shell_port,
107 124 iopub_port=iopub_port,
108 125 stdin_port=stdin_port,
126 control_port=control_port,
109 127 hb_port=hb_port,
110 128 )
111 129 cfg['ip'] = ip
@@ -286,7 +304,9 b' def connect_qtconsole(connection_file=None, argv=None, profile=None):'
286 304 "qtconsoleapp.main()"
287 305 ])
288 306
289 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
307 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
308 stdout=PIPE, stderr=PIPE, close_fds=True,
309 )
290 310
291 311
292 312 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
@@ -337,6 +357,179 b' def tunnel_to_kernel(connection_info, sshserver, sshkey=None):'
337 357
338 358 return tuple(lports)
339 359
360
361 #-----------------------------------------------------------------------------
362 # Mixin for classes that work with connection files
363 #-----------------------------------------------------------------------------
364
365 channel_socket_types = {
366 'hb' : zmq.REQ,
367 'shell' : zmq.DEALER,
368 'iopub' : zmq.SUB,
369 'stdin' : zmq.DEALER,
370 'control': zmq.DEALER,
371 }
372
373 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
374
375 class ConnectionFileMixin(HasTraits):
376 """Mixin for configurable classes that work with connection files"""
377
378 # The addresses for the communication channels
379 connection_file = Unicode('')
380 _connection_file_written = Bool(False)
381
382 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
383
384 ip = Unicode(LOCALHOST, config=True,
385 help="""Set the kernel\'s IP address [default localhost].
386 If the IP address is something other than localhost, then
387 Consoles on other machines will be able to connect
388 to the Kernel, so be careful!"""
389 )
390
391 def _ip_default(self):
392 if self.transport == 'ipc':
393 if self.connection_file:
394 return os.path.splitext(self.connection_file)[0] + '-ipc'
395 else:
396 return 'kernel-ipc'
397 else:
398 return LOCALHOST
399
400 def _ip_changed(self, name, old, new):
401 if new == '*':
402 self.ip = '0.0.0.0'
403
404 # protected traits
405
406 shell_port = Integer(0)
407 iopub_port = Integer(0)
408 stdin_port = Integer(0)
409 control_port = Integer(0)
410 hb_port = Integer(0)
411
412 @property
413 def ports(self):
414 return [ getattr(self, name) for name in port_names ]
415
416 #--------------------------------------------------------------------------
417 # Connection and ipc file management
418 #--------------------------------------------------------------------------
419
420 def get_connection_info(self):
421 """return the connection info as a dict"""
422 return dict(
423 transport=self.transport,
424 ip=self.ip,
425 shell_port=self.shell_port,
426 iopub_port=self.iopub_port,
427 stdin_port=self.stdin_port,
428 hb_port=self.hb_port,
429 control_port=self.control_port,
430 )
431
432 def cleanup_connection_file(self):
433 """Cleanup connection file *if we wrote it*
434
435 Will not raise if the connection file was already removed somehow.
436 """
437 if self._connection_file_written:
438 # cleanup connection files on full shutdown of kernel we started
439 self._connection_file_written = False
440 try:
441 os.remove(self.connection_file)
442 except (IOError, OSError, AttributeError):
443 pass
444
445 def cleanup_ipc_files(self):
446 """Cleanup ipc files if we wrote them."""
447 if self.transport != 'ipc':
448 return
449 for port in self.ports:
450 ipcfile = "%s-%i" % (self.ip, port)
451 try:
452 os.remove(ipcfile)
453 except (IOError, OSError):
454 pass
455
456 def write_connection_file(self):
457 """Write connection info to JSON dict in self.connection_file."""
458 if self._connection_file_written:
459 return
460
461 self.connection_file, cfg = write_connection_file(self.connection_file,
462 transport=self.transport, ip=self.ip, key=self.session.key,
463 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
464 shell_port=self.shell_port, hb_port=self.hb_port,
465 control_port=self.control_port,
466 )
467 # write_connection_file also sets default ports:
468 for name in port_names:
469 setattr(self, name, cfg[name])
470
471 self._connection_file_written = True
472
473 def load_connection_file(self):
474 """Load connection info from JSON dict in self.connection_file."""
475 with open(self.connection_file) as f:
476 cfg = json.loads(f.read())
477
478 self.transport = cfg.get('transport', 'tcp')
479 self.ip = cfg['ip']
480 for name in port_names:
481 setattr(self, name, cfg[name])
482 self.session.key = str_to_bytes(cfg['key'])
483
484 #--------------------------------------------------------------------------
485 # Creating connected sockets
486 #--------------------------------------------------------------------------
487
488 def _make_url(self, channel):
489 """Make a ZeroMQ URL for a given channel."""
490 transport = self.transport
491 ip = self.ip
492 port = getattr(self, '%s_port' % channel)
493
494 if transport == 'tcp':
495 return "tcp://%s:%i" % (ip, port)
496 else:
497 return "%s://%s-%s" % (transport, ip, port)
498
499 def _create_connected_socket(self, channel, identity=None):
500 """Create a zmq Socket and connect it to the kernel."""
501 url = self._make_url(channel)
502 socket_type = channel_socket_types[channel]
503 self.log.info("Connecting to: %s" % url)
504 sock = self.context.socket(socket_type)
505 if identity:
506 sock.identity = identity
507 sock.connect(url)
508 return sock
509
510 def connect_iopub(self, identity=None):
511 """return zmq Socket connected to the IOPub channel"""
512 sock = self._create_connected_socket('iopub', identity=identity)
513 sock.setsockopt(zmq.SUBSCRIBE, b'')
514 return sock
515
516 def connect_shell(self, identity=None):
517 """return zmq Socket connected to the Shell channel"""
518 return self._create_connected_socket('shell', identity=identity)
519
520 def connect_stdin(self, identity=None):
521 """return zmq Socket connected to the StdIn channel"""
522 return self._create_connected_socket('stdin', identity=identity)
523
524 def connect_hb(self, identity=None):
525 """return zmq Socket connected to the Heartbeat channel"""
526 return self._create_connected_socket('hb', identity=identity)
527
528 def connect_control(self, identity=None):
529 """return zmq Socket connected to the Heartbeat channel"""
530 return self._create_connected_socket('control', identity=identity)
531
532
340 533 __all__ = [
341 534 'write_connection_file',
342 535 'get_connection_file',
@@ -344,4 +537,4 b' __all__ = ['
344 537 'get_connection_info',
345 538 'connect_qtconsole',
346 539 'tunnel_to_kernel',
347 ] No newline at end of file
540 ]
@@ -0,0 +1,10 b''
1 from .channels import (
2 InProcessShellChannel,
3 InProcessIOPubChannel,
4 InProcessStdInChannel,
5 InProcessHBChannel,
6 )
7
8 from .client import InProcessKernelClient
9 from .manager import InProcessKernelManager
10 from .blocking import BlockingInProcessKernelClient
@@ -1,4 +1,4 b''
1 """ Implements a fully blocking kernel manager.
1 """ Implements a fully blocking kernel client.
2 2
3 3 Useful for test suites and blocking terminal interfaces.
4 4 """
@@ -12,15 +12,19 b' Useful for test suites and blocking terminal interfaces.'
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 from __future__ import print_function
16 15
17 # Local imports.
16 # IPython imports
18 17 from IPython.utils.io import raw_print
19 18 from IPython.utils.traitlets import Type
20 from kernelmanager import InProcessKernelManager, InProcessShellChannel, \
21 InProcessIOPubChannel, InProcessStdInChannel
22 from IPython.kernel.blockingkernelmanager import BlockingChannelMixin
19 from IPython.kernel.blocking.channels import BlockingChannelMixin
23 20
21 # Local imports
22 from .channels import (
23 InProcessShellChannel,
24 InProcessIOPubChannel,
25 InProcessStdInChannel,
26 )
27 from .client import InProcessKernelClient
24 28
25 29 #-----------------------------------------------------------------------------
26 30 # Blocking kernel manager
@@ -33,7 +37,7 b' class BlockingInProcessIOPubChannel(BlockingChannelMixin, InProcessIOPubChannel)'
33 37 pass
34 38
35 39 class BlockingInProcessStdInChannel(BlockingChannelMixin, InProcessStdInChannel):
36
40
37 41 def call_handlers(self, msg):
38 42 """ Overridden for the in-process channel.
39 43
@@ -41,12 +45,12 b' class BlockingInProcessStdInChannel(BlockingChannelMixin, InProcessStdInChannel)'
41 45 """
42 46 msg_type = msg['header']['msg_type']
43 47 if msg_type == 'input_request':
44 _raw_input = self.manager.kernel._sys_raw_input
48 _raw_input = self.client.kernel._sys_raw_input
45 49 prompt = msg['content']['prompt']
46 50 raw_print(prompt, end='')
47 51 self.input(_raw_input())
48 52
49 class BlockingInProcessKernelManager(InProcessKernelManager):
53 class BlockingInProcessKernelClient(InProcessKernelClient):
50 54
51 55 # The classes to use for the various channels.
52 56 shell_channel_class = Type(BlockingInProcessShellChannel)
@@ -1,4 +1,4 b''
1 """ A kernel manager for in-process kernels. """
1 """ A kernel client for in-process kernels. """
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2012 The IPython Development Team
@@ -11,15 +11,13 b''
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 # Local imports.
15 from IPython.config.configurable import Configurable
16 from IPython.utils.traitlets import Any, Instance, Type
17 from IPython.kernel.kernelmanagerabc import (
14 # IPython imports
15 from IPython.kernel.channelsabc import (
18 16 ShellChannelABC, IOPubChannelABC,
19 17 HBChannelABC, StdInChannelABC,
20 KernelManagerABC
21 18 )
22 19
20 # Local imports
23 21 from .socket import DummySocket
24 22
25 23 #-----------------------------------------------------------------------------
@@ -28,10 +26,11 b' from .socket import DummySocket'
28 26
29 27 class InProcessChannel(object):
30 28 """Base class for in-process channels."""
29 proxy_methods = []
31 30
32 def __init__(self, manager):
31 def __init__(self, client):
33 32 super(InProcessChannel, self).__init__()
34 self.manager = manager
33 self.client = client
35 34 self._is_alive = False
36 35
37 36 #--------------------------------------------------------------------------
@@ -77,10 +76,17 b' class InProcessChannel(object):'
77 76
78 77
79 78 class InProcessShellChannel(InProcessChannel):
80 """See `IPython.kernel.kernelmanager.ShellChannel` for docstrings."""
79 """See `IPython.kernel.channels.ShellChannel` for docstrings."""
81 80
82 81 # flag for whether execute requests should be allowed to call raw_input
83 82 allow_stdin = True
83 proxy_methods = [
84 'execute',
85 'complete',
86 'object_info',
87 'history',
88 'shutdown',
89 ]
84 90
85 91 #--------------------------------------------------------------------------
86 92 # ShellChannel interface
@@ -94,26 +100,26 b' class InProcessShellChannel(InProcessChannel):'
94 100 user_variables=user_variables,
95 101 user_expressions=user_expressions,
96 102 allow_stdin=allow_stdin)
97 msg = self.manager.session.msg('execute_request', content)
103 msg = self.client.session.msg('execute_request', content)
98 104 self._dispatch_to_kernel(msg)
99 105 return msg['header']['msg_id']
100 106
101 107 def complete(self, text, line, cursor_pos, block=None):
102 108 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
103 msg = self.manager.session.msg('complete_request', content)
109 msg = self.client.session.msg('complete_request', content)
104 110 self._dispatch_to_kernel(msg)
105 111 return msg['header']['msg_id']
106 112
107 113 def object_info(self, oname, detail_level=0):
108 114 content = dict(oname=oname, detail_level=detail_level)
109 msg = self.manager.session.msg('object_info_request', content)
115 msg = self.client.session.msg('object_info_request', content)
110 116 self._dispatch_to_kernel(msg)
111 117 return msg['header']['msg_id']
112 118
113 119 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
114 120 content = dict(raw=raw, output=output,
115 121 hist_access_type=hist_access_type, **kwds)
116 msg = self.manager.session.msg('history_request', content)
122 msg = self.client.session.msg('history_request', content)
117 123 self._dispatch_to_kernel(msg)
118 124 return msg['header']['msg_id']
119 125
@@ -128,38 +134,40 b' class InProcessShellChannel(InProcessChannel):'
128 134 def _dispatch_to_kernel(self, msg):
129 135 """ Send a message to the kernel and handle a reply.
130 136 """
131 kernel = self.manager.kernel
137 kernel = self.client.kernel
132 138 if kernel is None:
133 139 raise RuntimeError('Cannot send request. No kernel exists.')
134 140
135 141 stream = DummySocket()
136 self.manager.session.send(stream, msg)
142 self.client.session.send(stream, msg)
137 143 msg_parts = stream.recv_multipart()
138 144 kernel.dispatch_shell(stream, msg_parts)
139 145
140 idents, reply_msg = self.manager.session.recv(stream, copy=False)
146 idents, reply_msg = self.client.session.recv(stream, copy=False)
141 147 self.call_handlers_later(reply_msg)
142 148
143 149
144 150 class InProcessIOPubChannel(InProcessChannel):
145 """See `IPython.kernel.kernelmanager.IOPubChannel` for docstrings."""
151 """See `IPython.kernel.channels.IOPubChannel` for docstrings."""
146 152
147 153 def flush(self, timeout=1.0):
148 154 pass
149 155
150 156
151 157 class InProcessStdInChannel(InProcessChannel):
152 """See `IPython.kernel.kernelmanager.StdInChannel` for docstrings."""
158 """See `IPython.kernel.channels.StdInChannel` for docstrings."""
159
160 proxy_methods = ['input']
153 161
154 162 def input(self, string):
155 kernel = self.manager.kernel
163 kernel = self.client.kernel
156 164 if kernel is None:
157 165 raise RuntimeError('Cannot send input reply. No kernel exists.')
158 166 kernel.raw_input_str = string
159 167
160 168
161 169 class InProcessHBChannel(InProcessChannel):
162 """See `IPython.kernel.kernelmanager.HBChannel` for docstrings."""
170 """See `IPython.kernel.channels.HBChannel` for docstrings."""
163 171
164 172 time_to_dead = 3.0
165 173
@@ -176,133 +184,6 b' class InProcessHBChannel(InProcessChannel):'
176 184 def is_beating(self):
177 185 return not self._pause
178 186
179
180 #-----------------------------------------------------------------------------
181 # Main kernel manager class
182 #-----------------------------------------------------------------------------
183
184 class InProcessKernelManager(Configurable):
185 """A manager for an in-process kernel.
186
187 This class implements the interface of
188 `IPython.kernel.kernelmanagerabc.KernelManagerABC` and allows
189 (asynchronous) frontends to be used seamlessly with an in-process kernel.
190
191 See `IPython.kernel.kernelmanager.KernelManager` for docstrings.
192 """
193
194 # The Session to use for building messages.
195 session = Instance('IPython.kernel.zmq.session.Session')
196 def _session_default(self):
197 from IPython.kernel.zmq.session import Session
198 return Session(config=self.config)
199
200 # The kernel process with which the KernelManager is communicating.
201 kernel = Instance('IPython.kernel.inprocess.ipkernel.InProcessKernel')
202
203 # The classes to use for the various channels.
204 shell_channel_class = Type(InProcessShellChannel)
205 iopub_channel_class = Type(InProcessIOPubChannel)
206 stdin_channel_class = Type(InProcessStdInChannel)
207 hb_channel_class = Type(InProcessHBChannel)
208
209 # Protected traits.
210 _shell_channel = Any
211 _iopub_channel = Any
212 _stdin_channel = Any
213 _hb_channel = Any
214
215 #--------------------------------------------------------------------------
216 # Channel management methods.
217 #--------------------------------------------------------------------------
218
219 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
220 if shell:
221 self.shell_channel.start()
222 if iopub:
223 self.iopub_channel.start()
224 if stdin:
225 self.stdin_channel.start()
226 self.shell_channel.allow_stdin = True
227 else:
228 self.shell_channel.allow_stdin = False
229 if hb:
230 self.hb_channel.start()
231
232 def stop_channels(self):
233 if self.shell_channel.is_alive():
234 self.shell_channel.stop()
235 if self.iopub_channel.is_alive():
236 self.iopub_channel.stop()
237 if self.stdin_channel.is_alive():
238 self.stdin_channel.stop()
239 if self.hb_channel.is_alive():
240 self.hb_channel.stop()
241
242 @property
243 def channels_running(self):
244 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
245 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
246
247 @property
248 def shell_channel(self):
249 if self._shell_channel is None:
250 self._shell_channel = self.shell_channel_class(self)
251 return self._shell_channel
252
253 @property
254 def iopub_channel(self):
255 if self._iopub_channel is None:
256 self._iopub_channel = self.iopub_channel_class(self)
257 return self._iopub_channel
258
259 @property
260 def stdin_channel(self):
261 if self._stdin_channel is None:
262 self._stdin_channel = self.stdin_channel_class(self)
263 return self._stdin_channel
264
265 @property
266 def hb_channel(self):
267 if self._hb_channel is None:
268 self._hb_channel = self.hb_channel_class(self)
269 return self._hb_channel
270
271 #--------------------------------------------------------------------------
272 # Kernel management methods:
273 #--------------------------------------------------------------------------
274
275 def start_kernel(self, **kwds):
276 from IPython.kernel.inprocess.ipkernel import InProcessKernel
277 self.kernel = InProcessKernel()
278 self.kernel.frontends.append(self)
279
280 def shutdown_kernel(self):
281 self._kill_kernel()
282
283 def restart_kernel(self, now=False, **kwds):
284 self.shutdown_kernel()
285 self.start_kernel(**kwds)
286
287 @property
288 def has_kernel(self):
289 return self.kernel is not None
290
291 def _kill_kernel(self):
292 self.kernel.frontends.remove(self)
293 self.kernel = None
294
295 def interrupt_kernel(self):
296 raise NotImplementedError("Cannot interrupt in-process kernel.")
297
298 def signal_kernel(self, signum):
299 raise NotImplementedError("Cannot signal in-process kernel.")
300
301 @property
302 def is_alive(self):
303 return True
304
305
306 187 #-----------------------------------------------------------------------------
307 188 # ABC Registration
308 189 #-----------------------------------------------------------------------------
@@ -311,4 +192,3 b' ShellChannelABC.register(InProcessShellChannel)'
311 192 IOPubChannelABC.register(InProcessIOPubChannel)
312 193 HBChannelABC.register(InProcessHBChannel)
313 194 StdInChannelABC.register(InProcessStdInChannel)
314 KernelManagerABC.register(InProcessKernelManager)
@@ -37,7 +37,8 b' class InProcessKernel(Kernel):'
37 37
38 38 # The frontends connected to this kernel.
39 39 frontends = List(
40 Instance('IPython.kernel.inprocess.kernelmanager.InProcessKernelManager'))
40 Instance('IPython.kernel.inprocess.client.InProcessKernelClient')
41 )
41 42
42 43 # The GUI environment that the kernel is running under. This need not be
43 44 # specified for the normal operation for the kernel, but is required for
@@ -16,8 +16,8 b' import sys'
16 16 import unittest
17 17
18 18 # Local imports
19 from IPython.kernel.inprocess.blockingkernelmanager import \
20 BlockingInProcessKernelManager
19 from IPython.kernel.inprocess.blocking import BlockingInProcessKernelClient
20 from IPython.kernel.inprocess.manager import InProcessKernelManager
21 21 from IPython.kernel.inprocess.ipkernel import InProcessKernel
22 22 from IPython.testing.decorators import skipif_not_matplotlib
23 23 from IPython.utils.io import capture_output
@@ -29,33 +29,35 b' from IPython.utils import py3compat'
29 29
30 30 class InProcessKernelTestCase(unittest.TestCase):
31 31
32 def setUp(self):
33 self.km = InProcessKernelManager()
34 self.km.start_kernel()
35 self.kc = BlockingInProcessKernelClient(kernel=self.km.kernel)
36 self.kc.start_channels()
37
32 38 @skipif_not_matplotlib
33 39 def test_pylab(self):
34 40 """ Does pylab work in the in-process kernel?
35 41 """
36 km = BlockingInProcessKernelManager()
37 km.start_kernel()
38 km.shell_channel.execute('%pylab')
39 msg = get_stream_message(km)
42 kc = self.kc
43 kc.execute('%pylab')
44 msg = get_stream_message(kc)
40 45 self.assert_('Welcome to pylab' in msg['content']['data'])
41 46
42 47 def test_raw_input(self):
43 48 """ Does the in-process kernel handle raw_input correctly?
44 49 """
45 km = BlockingInProcessKernelManager()
46 km.start_kernel()
47
48 50 io = StringIO('foobar\n')
49 51 sys_stdin = sys.stdin
50 52 sys.stdin = io
51 53 try:
52 54 if py3compat.PY3:
53 km.shell_channel.execute('x = input()')
55 self.kc.execute('x = input()')
54 56 else:
55 km.shell_channel.execute('x = raw_input()')
57 self.kc.execute('x = raw_input()')
56 58 finally:
57 59 sys.stdin = sys_stdin
58 self.assertEqual(km.kernel.shell.user_ns.get('x'), 'foobar')
60 self.assertEqual(self.km.kernel.shell.user_ns.get('x'), 'foobar')
59 61
60 62 def test_stdout(self):
61 63 """ Does the in-process kernel correctly capture IO?
@@ -66,21 +68,21 b' class InProcessKernelTestCase(unittest.TestCase):'
66 68 kernel.shell.run_cell('print("foo")')
67 69 self.assertEqual(io.stdout, 'foo\n')
68 70
69 km = BlockingInProcessKernelManager(kernel=kernel)
70 kernel.frontends.append(km)
71 km.shell_channel.execute('print("bar")')
72 msg = get_stream_message(km)
71 kc = BlockingInProcessKernelClient(kernel=kernel)
72 kernel.frontends.append(kc)
73 kc.shell_channel.execute('print("bar")')
74 msg = get_stream_message(kc)
73 75 self.assertEqual(msg['content']['data'], 'bar\n')
74 76
75 77 #-----------------------------------------------------------------------------
76 78 # Utility functions
77 79 #-----------------------------------------------------------------------------
78 80
79 def get_stream_message(kernel_manager, timeout=5):
81 def get_stream_message(kernel_client, timeout=5):
80 82 """ Gets a single stream message synchronously from the sub channel.
81 83 """
82 84 while True:
83 msg = kernel_manager.iopub_channel.get_msg(timeout=timeout)
85 msg = kernel_client.get_iopub_msg(timeout=timeout)
84 86 if msg['header']['msg_type'] == 'stream':
85 87 return msg
86 88
@@ -14,9 +14,9 b' from __future__ import print_function'
14 14 import unittest
15 15
16 16 # Local imports
17 from IPython.kernel.inprocess.blockingkernelmanager import \
18 BlockingInProcessKernelManager
17 from IPython.kernel.inprocess.blocking import BlockingInProcessKernelClient
19 18 from IPython.kernel.inprocess.ipkernel import InProcessKernel
19 from IPython.kernel.inprocess.manager import InProcessKernelManager
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Test case
@@ -24,20 +24,22 b' from IPython.kernel.inprocess.ipkernel import InProcessKernel'
24 24
25 25 class InProcessKernelManagerTestCase(unittest.TestCase):
26 26
27 def test_inteface(self):
27 def test_interface(self):
28 28 """ Does the in-process kernel manager implement the basic KM interface?
29 29 """
30 km = BlockingInProcessKernelManager()
31 self.assert_(not km.channels_running)
30 km = InProcessKernelManager()
32 31 self.assert_(not km.has_kernel)
33 32
34 km.start_channels()
35 self.assert_(km.channels_running)
36
37 33 km.start_kernel()
38 34 self.assert_(km.has_kernel)
39 35 self.assert_(km.kernel is not None)
40 36
37 kc = BlockingInProcessKernelClient(kernel=km.kernel)
38 self.assert_(not kc.channels_running)
39
40 kc.start_channels()
41 self.assert_(kc.channels_running)
42
41 43 old_kernel = km.kernel
42 44 km.restart_kernel()
43 45 self.assert_(km.kernel is not None)
@@ -49,37 +51,43 b' class InProcessKernelManagerTestCase(unittest.TestCase):'
49 51 self.assertRaises(NotImplementedError, km.interrupt_kernel)
50 52 self.assertRaises(NotImplementedError, km.signal_kernel, 9)
51 53
52 km.stop_channels()
53 self.assert_(not km.channels_running)
54 kc.stop_channels()
55 self.assert_(not kc.channels_running)
54 56
55 57 def test_execute(self):
56 58 """ Does executing code in an in-process kernel work?
57 59 """
58 km = BlockingInProcessKernelManager()
60 km = InProcessKernelManager()
59 61 km.start_kernel()
60 km.shell_channel.execute('foo = 1')
62 kc = BlockingInProcessKernelClient(kernel=km.kernel)
63 kc.start_channels()
64 kc.execute('foo = 1')
61 65 self.assertEquals(km.kernel.shell.user_ns['foo'], 1)
62 66
63 67 def test_complete(self):
64 68 """ Does requesting completion from an in-process kernel work?
65 69 """
66 km = BlockingInProcessKernelManager()
70 km = InProcessKernelManager()
67 71 km.start_kernel()
72 kc = BlockingInProcessKernelClient(kernel=km.kernel)
73 kc.start_channels()
68 74 km.kernel.shell.push({'my_bar': 0, 'my_baz': 1})
69 km.shell_channel.complete('my_ba', 'my_ba', 5)
70 msg = km.shell_channel.get_msg()
71 self.assertEquals(msg['header']['msg_type'], 'complete_reply')
72 self.assertEquals(sorted(msg['content']['matches']),
75 kc.complete('my_ba', 'my_ba', 5)
76 msg = kc.get_shell_msg()
77 self.assertEqual(msg['header']['msg_type'], 'complete_reply')
78 self.assertEqual(sorted(msg['content']['matches']),
73 79 ['my_bar', 'my_baz'])
74 80
75 81 def test_object_info(self):
76 82 """ Does requesting object information from an in-process kernel work?
77 83 """
78 km = BlockingInProcessKernelManager()
84 km = InProcessKernelManager()
79 85 km.start_kernel()
86 kc = BlockingInProcessKernelClient(kernel=km.kernel)
87 kc.start_channels()
80 88 km.kernel.shell.user_ns['foo'] = 1
81 km.shell_channel.object_info('foo')
82 msg = km.shell_channel.get_msg()
89 kc.object_info('foo')
90 msg = kc.get_shell_msg()
83 91 self.assertEquals(msg['header']['msg_type'], 'object_info_reply')
84 92 self.assertEquals(msg['content']['name'], 'foo')
85 93 self.assertEquals(msg['content']['type_name'], 'int')
@@ -87,11 +95,13 b' class InProcessKernelManagerTestCase(unittest.TestCase):'
87 95 def test_history(self):
88 96 """ Does requesting history from an in-process kernel work?
89 97 """
90 km = BlockingInProcessKernelManager()
98 km = InProcessKernelManager()
91 99 km.start_kernel()
92 km.shell_channel.execute('%who')
93 km.shell_channel.history(hist_access_type='tail', n=1)
94 msg = km.shell_channel.get_msgs()[-1]
100 kc = BlockingInProcessKernelClient(kernel=km.kernel)
101 kc.start_channels()
102 kc.execute('%who')
103 kc.history(hist_access_type='tail', n=1)
104 msg = kc.shell_channel.get_msgs()[-1]
95 105 self.assertEquals(msg['header']['msg_type'], 'history_reply')
96 106 history = msg['content']['history']
97 107 self.assertEquals(len(history), 1)
@@ -220,7 +220,6 b' class KernelManagerABC(object):'
220 220 def signal_kernel(self, signum):
221 221 pass
222 222
223 @abc.abstractproperty
223 @abc.abstractmethod
224 224 def is_alive(self):
225 225 pass
226
@@ -22,13 +22,13 b' import os'
22 22 import uuid
23 23
24 24 import zmq
25 from zmq.eventloop.zmqstream import ZMQStream
26 25
27 26 from IPython.config.configurable import LoggingConfigurable
28 27 from IPython.utils.importstring import import_item
29 28 from IPython.utils.traitlets import (
30 Instance, Dict, Unicode, Any, DottedObjectName,
29 Instance, Dict, Unicode, Any, DottedObjectName, Bool
31 30 )
31
32 32 #-----------------------------------------------------------------------------
33 33 # Classes
34 34 #-----------------------------------------------------------------------------
@@ -37,11 +37,28 b' class DuplicateKernelError(Exception):'
37 37 pass
38 38
39 39
40
41 def kernel_method(f):
42 """decorator for proxying MKM.method(kernel_id) to individual KMs by ID"""
43 def wrapped(self, kernel_id, *args, **kwargs):
44 # get the kernel
45 km = self.get_kernel(kernel_id)
46 method = getattr(km, f.__name__)
47 # call the kernel's method
48 r = method(*args, **kwargs)
49 # last thing, call anything defined in the actual class method
50 # such as logging messages
51 f(self, kernel_id, *args, **kwargs)
52 # return the method result
53 return r
54 return wrapped
55
56
40 57 class MultiKernelManager(LoggingConfigurable):
41 58 """A class for managing multiple kernels."""
42 59
43 60 kernel_manager_class = DottedObjectName(
44 "IPython.kernel.blockingkernelmanager.BlockingKernelManager", config=True,
61 "IPython.kernel.ioloop.IOLoopKernelManager", config=True,
45 62 help="""The kernel manager class. This is configurable to allow
46 63 subclassing of the KernelManager for customized behavior.
47 64 """
@@ -56,7 +73,7 b' class MultiKernelManager(LoggingConfigurable):'
56 73 context = Instance('zmq.Context')
57 74 def _context_default(self):
58 75 return zmq.Context.instance()
59
76
60 77 connection_dir = Unicode('')
61 78
62 79 _kernels = Dict()
@@ -93,14 +110,13 b' class MultiKernelManager(LoggingConfigurable):'
93 110 # including things like its transport and ip.
94 111 km = self.kernel_manager_factory(connection_file=os.path.join(
95 112 self.connection_dir, "kernel-%s.json" % kernel_id),
96 config=self.config,
113 config=self.config, autorestart=True, log=self.log
97 114 )
98 115 km.start_kernel(**kwargs)
99 # start just the shell channel, needed for graceful restart
100 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
101 116 self._kernels[kernel_id] = km
102 117 return kernel_id
103 118
119 @kernel_method
104 120 def shutdown_kernel(self, kernel_id, now=False):
105 121 """Shutdown a kernel by its kernel uuid.
106 122
@@ -111,16 +127,25 b' class MultiKernelManager(LoggingConfigurable):'
111 127 now : bool
112 128 Should the kernel be shutdown forcibly using a signal.
113 129 """
114 k = self.get_kernel(kernel_id)
115 k.shutdown_kernel(now=now)
116 k.shell_channel.stop()
117 del self._kernels[kernel_id]
130 self.log.info("Kernel shutdown: %s" % kernel_id)
131 self.remove_kernel(kernel_id)
132
133 def remove_kernel(self, kernel_id):
134 """remove a kernel from our mapping.
135
136 Mainly so that a kernel can be removed if it is already dead,
137 without having to call shutdown_kernel.
138
139 The kernel object is returned.
140 """
141 return self._kernels.pop(kernel_id)
118 142
119 143 def shutdown_all(self, now=False):
120 144 """Shutdown all kernels."""
121 145 for kid in self.list_kernel_ids():
122 146 self.shutdown_kernel(kid, now=now)
123 147
148 @kernel_method
124 149 def interrupt_kernel(self, kernel_id):
125 150 """Interrupt (SIGINT) the kernel by its uuid.
126 151
@@ -129,8 +154,9 b' class MultiKernelManager(LoggingConfigurable):'
129 154 kernel_id : uuid
130 155 The id of the kernel to interrupt.
131 156 """
132 return self.get_kernel(kernel_id).interrupt_kernel()
157 self.log.info("Kernel interrupted: %s" % kernel_id)
133 158
159 @kernel_method
134 160 def signal_kernel(self, kernel_id, signum):
135 161 """Sends a signal to the kernel by its uuid.
136 162
@@ -142,8 +168,9 b' class MultiKernelManager(LoggingConfigurable):'
142 168 kernel_id : uuid
143 169 The id of the kernel to signal.
144 170 """
145 return self.get_kernel(kernel_id).signal_kernel(signum)
171 self.log.info("Signaled Kernel %s with %s" % (kernel_id, signum))
146 172
173 @kernel_method
147 174 def restart_kernel(self, kernel_id):
148 175 """Restart a kernel by its uuid, keeping the same ports.
149 176
@@ -152,7 +179,25 b' class MultiKernelManager(LoggingConfigurable):'
152 179 kernel_id : uuid
153 180 The id of the kernel to interrupt.
154 181 """
155 return self.get_kernel(kernel_id).restart_kernel()
182 self.log.info("Kernel restarted: %s" % kernel_id)
183
184 @kernel_method
185 def is_alive(self, kernel_id):
186 """Is the kernel alive.
187
188 This calls KernelManager.is_alive() which calls Popen.poll on the
189 actual kernel subprocess.
190
191 Parameters
192 ==========
193 kernel_id : uuid
194 The id of the kernel.
195 """
196
197 def _check_kernel_id(self, kernel_id):
198 """check that a kernel id is valid"""
199 if kernel_id not in self:
200 raise KeyError("Kernel with id not found: %s" % kernel_id)
156 201
157 202 def get_kernel(self, kernel_id):
158 203 """Get the single KernelManager object for a kernel by its uuid.
@@ -162,12 +207,18 b' class MultiKernelManager(LoggingConfigurable):'
162 207 kernel_id : uuid
163 208 The id of the kernel.
164 209 """
165 km = self._kernels.get(kernel_id)
166 if km is not None:
167 return km
168 else:
169 raise KeyError("Kernel with id not found: %s" % kernel_id)
210 self._check_kernel_id(kernel_id)
211 return self._kernels[kernel_id]
212
213 @kernel_method
214 def add_restart_callback(self, kernel_id, callback, event='restart'):
215 """add a callback for the KernelRestarter"""
216
217 @kernel_method
218 def remove_restart_callback(self, kernel_id, callback, event='restart'):
219 """remove a callback for the KernelRestarter"""
170 220
221 @kernel_method
171 222 def get_connection_info(self, kernel_id):
172 223 """Return a dictionary of connection data for a kernel.
173 224
@@ -184,76 +235,67 b' class MultiKernelManager(LoggingConfigurable):'
184 235 numbers of the different channels (stdin_port, iopub_port,
185 236 shell_port, hb_port).
186 237 """
187 km = self.get_kernel(kernel_id)
188 return dict(transport=km.transport,
189 ip=km.ip,
190 shell_port=km.shell_port,
191 iopub_port=km.iopub_port,
192 stdin_port=km.stdin_port,
193 hb_port=km.hb_port,
194 )
195
196 def _make_url(self, transport, ip, port):
197 """Make a ZeroMQ URL for a given transport, ip and port."""
198 if transport == 'tcp':
199 return "tcp://%s:%i" % (ip, port)
200 else:
201 return "%s://%s-%s" % (transport, ip, port)
202
203 def _create_connected_stream(self, kernel_id, socket_type, channel):
204 """Create a connected ZMQStream for a kernel."""
205 cinfo = self.get_connection_info(kernel_id)
206 url = self._make_url(cinfo['transport'], cinfo['ip'],
207 cinfo['%s_port' % channel]
208 )
209 sock = self.context.socket(socket_type)
210 self.log.info("Connecting to: %s" % url)
211 sock.connect(url)
212 return ZMQStream(sock)
213 238
214 def create_iopub_stream(self, kernel_id):
215 """Return a ZMQStream object connected to the iopub channel.
239 @kernel_method
240 def connect_iopub(self, kernel_id, identity=None):
241 """Return a zmq Socket connected to the iopub channel.
216 242
217 243 Parameters
218 244 ==========
219 245 kernel_id : uuid
220 The id of the kernel.
246 The id of the kernel
247 identity : bytes (optional)
248 The zmq identity of the socket
221 249
222 250 Returns
223 251 =======
224 stream : ZMQStream
252 stream : zmq Socket or ZMQStream
225 253 """
226 iopub_stream = self._create_connected_stream(kernel_id, zmq.SUB, 'iopub')
227 iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
228 return iopub_stream
229 254
230 def create_shell_stream(self, kernel_id):
231 """Return a ZMQStream object connected to the shell channel.
255 @kernel_method
256 def connect_shell(self, kernel_id, identity=None):
257 """Return a zmq Socket connected to the shell channel.
232 258
233 259 Parameters
234 260 ==========
235 261 kernel_id : uuid
236 The id of the kernel.
262 The id of the kernel
263 identity : bytes (optional)
264 The zmq identity of the socket
237 265
238 266 Returns
239 267 =======
240 stream : ZMQStream
268 stream : zmq Socket or ZMQStream
241 269 """
242 shell_stream = self._create_connected_stream(kernel_id, zmq.DEALER, 'shell')
243 return shell_stream
244 270
245 def create_hb_stream(self, kernel_id):
246 """Return a ZMQStream object connected to the hb channel.
271 @kernel_method
272 def connect_stdin(self, kernel_id, identity=None):
273 """Return a zmq Socket connected to the stdin channel.
247 274
248 275 Parameters
249 276 ==========
250 277 kernel_id : uuid
251 The id of the kernel.
278 The id of the kernel
279 identity : bytes (optional)
280 The zmq identity of the socket
252 281
253 282 Returns
254 283 =======
255 stream : ZMQStream
284 stream : zmq Socket or ZMQStream
256 285 """
257 hb_stream = self._create_connected_stream(kernel_id, zmq.REQ, 'hb')
258 return hb_stream
259 286
287 @kernel_method
288 def connect_hb(self, kernel_id, identity=None):
289 """Return a zmq Socket connected to the hb channel.
290
291 Parameters
292 ==========
293 kernel_id : uuid
294 The id of the kernel
295 identity : bytes (optional)
296 The zmq identity of the socket
297
298 Returns
299 =======
300 stream : zmq Socket or ZMQStream
301 """
@@ -7,12 +7,14 b' from unittest import TestCase'
7 7 from IPython.testing import decorators as dec
8 8
9 9 from IPython.config.loader import Config
10 from IPython.kernel.kernelmanager import KernelManager
10 from IPython.kernel import KernelManager
11 11
12 12 class TestKernelManager(TestCase):
13 13
14 14 def _get_tcp_km(self):
15 return KernelManager()
15 c = Config()
16 km = KernelManager(config=c)
17 return km
16 18
17 19 def _get_ipc_km(self):
18 20 c = Config()
@@ -23,8 +25,9 b' class TestKernelManager(TestCase):'
23 25
24 26 def _run_lifecycle(self, km):
25 27 km.start_kernel(stdout=PIPE, stderr=PIPE)
26 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
28 self.assertTrue(km.is_alive())
27 29 km.restart_kernel()
30 self.assertTrue(km.is_alive())
28 31 # We need a delay here to give the restarting kernel a chance to
29 32 # restart. Otherwise, the interrupt will kill it, causing the test
30 33 # suite to hang. The reason it *hangs* is that the shutdown
@@ -35,7 +38,6 b' class TestKernelManager(TestCase):'
35 38 km.interrupt_kernel()
36 39 self.assertTrue(isinstance(km, KernelManager))
37 40 km.shutdown_kernel()
38 km.shell_channel.stop()
39 41
40 42 def test_tcp_lifecycle(self):
41 43 km = self._get_tcp_km()
@@ -15,7 +15,7 b' from Queue import Empty'
15 15
16 16 import nose.tools as nt
17 17
18 from ..blockingkernelmanager import BlockingKernelManager
18 from IPython.kernel import KernelManager, BlockingKernelClient
19 19
20 20
21 21 from IPython.testing import decorators as dec
@@ -29,28 +29,29 b' from IPython.utils.traitlets import ('
29 29 #-----------------------------------------------------------------------------
30 30
31 31 def setup():
32 global KM
33 KM = BlockingKernelManager()
34
32 global KM, KC
33 KM = KernelManager()
34 KM.client_factory = BlockingKernelClient
35 35 KM.start_kernel(stdout=PIPE, stderr=PIPE)
36 KM.start_channels()
36 KC = KM.client()
37 KC.start_channels()
37 38
38 39 # wait for kernel to be ready
39 KM.shell_channel.execute("pass")
40 KM.shell_channel.get_msg(block=True, timeout=5)
40 KC.execute("pass")
41 KC.get_shell_msg(block=True, timeout=5)
41 42 flush_channels()
42 43
43 44
44 45 def teardown():
45 KM.stop_channels()
46 KC.stop_channels()
46 47 KM.shutdown_kernel()
47 48
48 49
49 def flush_channels(km=None):
50 if km is None:
51 km = KM
50 def flush_channels(kc=None):
52 51 """flush any messages waiting on the queue"""
53 for channel in (km.shell_channel, km.iopub_channel):
52 if kc is None:
53 kc = KC
54 for channel in (kc.shell_channel, kc.iopub_channel):
54 55 while True:
55 56 try:
56 57 msg = channel.get_msg(block=True, timeout=0.1)
@@ -60,22 +61,17 b' def flush_channels(km=None):'
60 61 list(validate_message(msg))
61 62
62 63
63 def execute(code='', km=None, **kwargs):
64 def execute(code='', kc=None, **kwargs):
64 65 """wrapper for doing common steps for validating an execution request"""
65 if km is None:
66 km = KM
67 shell = km.shell_channel
68 sub = km.iopub_channel
69
70 msg_id = shell.execute(code=code, **kwargs)
71 reply = shell.get_msg(timeout=2)
66 msg_id = KC.execute(code=code, **kwargs)
67 reply = KC.get_shell_msg(timeout=2)
72 68 list(validate_message(reply, 'execute_reply', msg_id))
73 busy = sub.get_msg(timeout=2)
69 busy = KC.get_iopub_msg(timeout=2)
74 70 list(validate_message(busy, 'status', msg_id))
75 71 nt.assert_equal(busy['content']['execution_state'], 'busy')
76 72
77 73 if not kwargs.get('silent'):
78 pyin = sub.get_msg(timeout=2)
74 pyin = KC.get_iopub_msg(timeout=2)
79 75 list(validate_message(pyin, 'pyin', msg_id))
80 76 nt.assert_equal(pyin['content']['code'], code)
81 77
@@ -192,7 +188,7 b' class ArgSpec(Reference):'
192 188
193 189
194 190 class Status(Reference):
195 execution_state = Enum((u'busy', u'idle'))
191 execution_state = Enum((u'busy', u'idle', u'starting'))
196 192
197 193
198 194 class CompleteReply(Reference):
@@ -301,9 +297,8 b' def validate_message(msg, msg_type=None, parent=None):'
301 297 def test_execute():
302 298 flush_channels()
303 299
304 shell = KM.shell_channel
305 msg_id = shell.execute(code='x=1')
306 reply = shell.get_msg(timeout=2)
300 msg_id = KC.execute(code='x=1')
301 reply = KC.get_shell_msg(timeout=2)
307 302 for tst in validate_message(reply, 'execute_reply', msg_id):
308 303 yield tst
309 304
@@ -314,23 +309,23 b' def test_execute_silent():'
314 309 msg_id, reply = execute(code='x=1', silent=True)
315 310
316 311 # flush status=idle
317 status = KM.iopub_channel.get_msg(timeout=2)
312 status = KC.iopub_channel.get_msg(timeout=2)
318 313 for tst in validate_message(status, 'status', msg_id):
319 314 yield tst
320 315 nt.assert_equal(status['content']['execution_state'], 'idle')
321 316
322 yield nt.assert_raises(Empty, KM.iopub_channel.get_msg, timeout=0.1)
317 yield nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
323 318 count = reply['execution_count']
324 319
325 320 msg_id, reply = execute(code='x=2', silent=True)
326 321
327 322 # flush status=idle
328 status = KM.iopub_channel.get_msg(timeout=2)
323 status = KC.iopub_channel.get_msg(timeout=2)
329 324 for tst in validate_message(status, 'status', msg_id):
330 325 yield tst
331 326 yield nt.assert_equal(status['content']['execution_state'], 'idle')
332 327
333 yield nt.assert_raises(Empty, KM.iopub_channel.get_msg, timeout=0.1)
328 yield nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
334 329 count_2 = reply['execution_count']
335 330 yield nt.assert_equal(count_2, count)
336 331
@@ -343,7 +338,7 b' def test_execute_error():'
343 338 yield nt.assert_equal(reply['status'], 'error')
344 339 yield nt.assert_equal(reply['ename'], 'ZeroDivisionError')
345 340
346 pyerr = KM.iopub_channel.get_msg(timeout=2)
341 pyerr = KC.iopub_channel.get_msg(timeout=2)
347 342 for tst in validate_message(pyerr, 'pyerr', msg_id):
348 343 yield tst
349 344
@@ -382,10 +377,8 b' def test_user_expressions():'
382 377 def test_oinfo():
383 378 flush_channels()
384 379
385 shell = KM.shell_channel
386
387 msg_id = shell.object_info('a')
388 reply = shell.get_msg(timeout=2)
380 msg_id = KC.object_info('a')
381 reply = KC.get_shell_msg(timeout=2)
389 382 for tst in validate_message(reply, 'object_info_reply', msg_id):
390 383 yield tst
391 384
@@ -394,12 +387,10 b' def test_oinfo():'
394 387 def test_oinfo_found():
395 388 flush_channels()
396 389
397 shell = KM.shell_channel
398
399 390 msg_id, reply = execute(code='a=5')
400 391
401 msg_id = shell.object_info('a')
402 reply = shell.get_msg(timeout=2)
392 msg_id = KC.object_info('a')
393 reply = KC.get_shell_msg(timeout=2)
403 394 for tst in validate_message(reply, 'object_info_reply', msg_id):
404 395 yield tst
405 396 content = reply['content']
@@ -412,12 +403,10 b' def test_oinfo_found():'
412 403 def test_oinfo_detail():
413 404 flush_channels()
414 405
415 shell = KM.shell_channel
416
417 406 msg_id, reply = execute(code='ip=get_ipython()')
418 407
419 msg_id = shell.object_info('ip.object_inspect', detail_level=2)
420 reply = shell.get_msg(timeout=2)
408 msg_id = KC.object_info('ip.object_inspect', detail_level=2)
409 reply = KC.get_shell_msg(timeout=2)
421 410 for tst in validate_message(reply, 'object_info_reply', msg_id):
422 411 yield tst
423 412 content = reply['content']
@@ -431,10 +420,8 b' def test_oinfo_detail():'
431 420 def test_oinfo_not_found():
432 421 flush_channels()
433 422
434 shell = KM.shell_channel
435
436 msg_id = shell.object_info('dne')
437 reply = shell.get_msg(timeout=2)
423 msg_id = KC.object_info('dne')
424 reply = KC.get_shell_msg(timeout=2)
438 425 for tst in validate_message(reply, 'object_info_reply', msg_id):
439 426 yield tst
440 427 content = reply['content']
@@ -445,12 +432,10 b' def test_oinfo_not_found():'
445 432 def test_complete():
446 433 flush_channels()
447 434
448 shell = KM.shell_channel
449
450 435 msg_id, reply = execute(code="alpha = albert = 5")
451 436
452 msg_id = shell.complete('al', 'al', 2)
453 reply = shell.get_msg(timeout=2)
437 msg_id = KC.complete('al', 'al', 2)
438 reply = KC.get_shell_msg(timeout=2)
454 439 for tst in validate_message(reply, 'complete_reply', msg_id):
455 440 yield tst
456 441 matches = reply['content']['matches']
@@ -462,10 +447,8 b' def test_complete():'
462 447 def test_kernel_info_request():
463 448 flush_channels()
464 449
465 shell = KM.shell_channel
466
467 msg_id = shell.kernel_info()
468 reply = shell.get_msg(timeout=2)
450 msg_id = KC.kernel_info()
451 reply = KC.get_shell_msg(timeout=2)
469 452 for tst in validate_message(reply, 'kernel_info_reply', msg_id):
470 453 yield tst
471 454
@@ -479,7 +462,7 b' def test_stream():'
479 462
480 463 msg_id, reply = execute("print('hi')")
481 464
482 stdout = KM.iopub_channel.get_msg(timeout=2)
465 stdout = KC.iopub_channel.get_msg(timeout=2)
483 466 for tst in validate_message(stdout, 'stream', msg_id):
484 467 yield tst
485 468 content = stdout['content']
@@ -493,7 +476,7 b' def test_display_data():'
493 476
494 477 msg_id, reply = execute("from IPython.core.display import display; display(1)")
495 478
496 display = KM.iopub_channel.get_msg(timeout=2)
479 display = KC.iopub_channel.get_msg(timeout=2)
497 480 for tst in validate_message(display, 'display_data', parent=msg_id):
498 481 yield tst
499 482 data = display['content']['data']
@@ -8,13 +8,15 b' from IPython.testing import decorators as dec'
8 8
9 9 from IPython.config.loader import Config
10 10 from IPython.utils.localinterfaces import LOCALHOST
11 from IPython.kernel.kernelmanager import KernelManager
11 from IPython.kernel import KernelManager
12 12 from IPython.kernel.multikernelmanager import MultiKernelManager
13 13
14 14 class TestKernelManager(TestCase):
15 15
16 16 def _get_tcp_km(self):
17 return MultiKernelManager()
17 c = Config()
18 km = MultiKernelManager(config=c)
19 return km
18 20
19 21 def _get_ipc_km(self):
20 22 c = Config()
@@ -25,10 +27,12 b' class TestKernelManager(TestCase):'
25 27
26 28 def _run_lifecycle(self, km):
27 29 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
30 self.assertTrue(km.is_alive(kid))
28 31 self.assertTrue(kid in km)
29 32 self.assertTrue(kid in km.list_kernel_ids())
30 33 self.assertEqual(len(km),1)
31 34 km.restart_kernel(kid)
35 self.assertTrue(km.is_alive(kid))
32 36 self.assertTrue(kid in km.list_kernel_ids())
33 37 # We need a delay here to give the restarting kernel a chance to
34 38 # restart. Otherwise, the interrupt will kill it, causing the test
@@ -51,13 +55,13 b' class TestKernelManager(TestCase):'
51 55 self.assertEqual(ip, cinfo['ip'])
52 56 self.assertTrue('stdin_port' in cinfo)
53 57 self.assertTrue('iopub_port' in cinfo)
54 stream = km.create_iopub_stream(kid)
58 stream = km.connect_iopub(kid)
55 59 stream.close()
56 60 self.assertTrue('shell_port' in cinfo)
57 stream = km.create_shell_stream(kid)
61 stream = km.connect_shell(kid)
58 62 stream.close()
59 63 self.assertTrue('hb_port' in cinfo)
60 stream = km.create_hb_stream(kid)
64 stream = km.connect_hb(kid)
61 65 stream.close()
62 66 km.shutdown_kernel(kid)
63 67
@@ -25,11 +25,17 b' from IPython import kernel'
25 25
26 26 @dec.parametric
27 27 def test_kms():
28 for base in ("", "Blocking", "Multi"):
28 for base in ("", "Multi"):
29 29 KM = base + "KernelManager"
30 30 yield nt.assert_true(KM in dir(kernel), KM)
31 31
32 32 @dec.parametric
33 def test_kcs():
34 for base in ("", "Blocking"):
35 KM = base + "KernelClient"
36 yield nt.assert_true(KM in dir(kernel), KM)
37
38 @dec.parametric
33 39 def test_launcher():
34 40 for name in launcher.__all__:
35 41 yield nt.assert_true(name in dir(kernel), name)
@@ -275,6 +275,9 b' class Kernel(Configurable):'
275 275
276 276 for s in self.shell_streams:
277 277 s.on_recv(make_dispatcher(s), copy=False)
278
279 # publish idle status
280 self._publish_status('starting')
278 281
279 282 def do_one_iteration(self):
280 283 """step eventloop just once"""
@@ -69,6 +69,7 b' kernel_aliases.update({'
69 69 'shell' : 'IPKernelApp.shell_port',
70 70 'iopub' : 'IPKernelApp.iopub_port',
71 71 'stdin' : 'IPKernelApp.stdin_port',
72 'control' : 'IPKernelApp.control_port',
72 73 'f' : 'IPKernelApp.connection_file',
73 74 'parent': 'IPKernelApp.parent',
74 75 'transport': 'IPKernelApp.transport',
@@ -145,7 +146,8 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
145 146 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
146 147 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
147 148 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
148 stdin_port = Integer(0, config=True, help="set the stdin (DEALER) port [default: random]")
149 stdin_port = Integer(0, config=True, help="set the stdin (ROUTER) port [default: random]")
150 control_port = Integer(0, config=True, help="set the control (ROUTER) port [default: random]")
149 151 connection_file = Unicode('', config=True,
150 152 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
151 153
@@ -227,7 +229,7 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
227 229 if self.ip == self._ip_default() and 'ip' in cfg:
228 230 # not overridden by config or cl_args
229 231 self.ip = cfg['ip']
230 for channel in ('hb', 'shell', 'iopub', 'stdin'):
232 for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
231 233 name = channel + '_port'
232 234 if getattr(self, name) == 0 and name in cfg:
233 235 # not overridden by config or cl_args
@@ -241,7 +243,7 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
241 243 self.log.debug("Writing connection file: %s", cf)
242 244 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
243 245 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
244 iopub_port=self.iopub_port)
246 iopub_port=self.iopub_port, control_port=self.control_port)
245 247
246 248 def cleanup_connection_file(self):
247 249 cf = self.abs_connection_file
@@ -257,7 +259,7 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
257 259 """cleanup ipc files if we wrote them"""
258 260 if self.transport != 'ipc':
259 261 return
260 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
262 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port, self.control_port):
261 263 ipcfile = "%s-%i" % (self.ip, port)
262 264 try:
263 265 os.remove(ipcfile)
@@ -282,15 +284,19 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
282 284
283 285 self.shell_socket = context.socket(zmq.ROUTER)
284 286 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
285 self.log.debug("shell ROUTER Channel on port: %i"%self.shell_port)
287 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
286 288
287 289 self.iopub_socket = context.socket(zmq.PUB)
288 290 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
289 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
291 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
290 292
291 293 self.stdin_socket = context.socket(zmq.ROUTER)
292 294 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
293 self.log.debug("stdin ROUTER Channel on port: %i"%self.stdin_port)
295 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
296
297 self.control_socket = context.socket(zmq.ROUTER)
298 self.control_port = self._bind_socket(self.control_socket, self.control_port)
299 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
294 300
295 301 def init_heartbeat(self):
296 302 """start the heart beating"""
@@ -299,7 +305,7 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
299 305 hb_ctx = zmq.Context()
300 306 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
301 307 self.hb_port = self.heartbeat.port
302 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
308 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
303 309 self.heartbeat.start()
304 310
305 311 # Helper to make it easier to connect to an existing kernel.
@@ -321,7 +327,8 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
321 327
322 328
323 329 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
324 stdin=self.stdin_port, hb=self.hb_port)
330 stdin=self.stdin_port, hb=self.hb_port,
331 control=self.control_port)
325 332
326 333 def init_session(self):
327 334 """create our session object"""
@@ -353,11 +360,12 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
353 360 def init_kernel(self):
354 361 """Create the Kernel object itself"""
355 362 shell_stream = ZMQStream(self.shell_socket)
363 control_stream = ZMQStream(self.control_socket)
356 364
357 365 kernel_factory = import_item(str(self.kernel_class))
358 366
359 367 kernel = kernel_factory(config=self.config, session=self.session,
360 shell_streams=[shell_stream],
368 shell_streams=[shell_stream, control_stream],
361 369 iopub_socket=self.iopub_socket,
362 370 stdin_socket=self.stdin_socket,
363 371 log=self.log,
@@ -22,7 +22,7 b' from subprocess import Popen, PIPE'
22 22
23 23 import nose.tools as nt
24 24
25 from IPython.kernel.blockingkernelmanager import BlockingKernelManager
25 from IPython.kernel import BlockingKernelClient
26 26 from IPython.utils import path, py3compat
27 27
28 28 #-------------------------------------------------------------------------------
@@ -83,14 +83,14 b' def setup_kernel(cmd):'
83 83 kernel.terminate()
84 84 raise IOError("Connection file %r never arrived" % connection_file)
85 85
86 km = BlockingKernelManager(connection_file=connection_file)
87 km.load_connection_file()
88 km.start_channels()
86 client = BlockingKernelClient(connection_file=connection_file)
87 client.load_connection_file()
88 client.start_channels()
89 89
90 90 try:
91 yield km
91 yield client
92 92 finally:
93 km.stop_channels()
93 client.stop_channels()
94 94 kernel.terminate()
95 95
96 96 def test_embed_kernel_basic():
@@ -105,23 +105,21 b' def test_embed_kernel_basic():'
105 105 '',
106 106 ])
107 107
108 with setup_kernel(cmd) as km:
109 shell = km.shell_channel
110
108 with setup_kernel(cmd) as client:
111 109 # oinfo a (int)
112 msg_id = shell.object_info('a')
113 msg = shell.get_msg(block=True, timeout=2)
110 msg_id = client.object_info('a')
111 msg = client.get_shell_msg(block=True, timeout=2)
114 112 content = msg['content']
115 113 nt.assert_true(content['found'])
116 114
117 msg_id = shell.execute("c=a*2")
118 msg = shell.get_msg(block=True, timeout=2)
115 msg_id = client.execute("c=a*2")
116 msg = client.get_shell_msg(block=True, timeout=2)
119 117 content = msg['content']
120 118 nt.assert_equal(content['status'], u'ok')
121 119
122 120 # oinfo c (should be 10)
123 msg_id = shell.object_info('c')
124 msg = shell.get_msg(block=True, timeout=2)
121 msg_id = client.object_info('c')
122 msg = client.get_shell_msg(block=True, timeout=2)
125 123 content = msg['content']
126 124 nt.assert_true(content['found'])
127 125 nt.assert_equal(content['string_form'], u'10')
@@ -138,26 +136,24 b' def test_embed_kernel_namespace():'
138 136 '',
139 137 ])
140 138
141 with setup_kernel(cmd) as km:
142 shell = km.shell_channel
143
139 with setup_kernel(cmd) as client:
144 140 # oinfo a (int)
145 msg_id = shell.object_info('a')
146 msg = shell.get_msg(block=True, timeout=2)
141 msg_id = client.object_info('a')
142 msg = client.get_shell_msg(block=True, timeout=2)
147 143 content = msg['content']
148 144 nt.assert_true(content['found'])
149 145 nt.assert_equal(content['string_form'], u'5')
150 146
151 147 # oinfo b (str)
152 msg_id = shell.object_info('b')
153 msg = shell.get_msg(block=True, timeout=2)
148 msg_id = client.object_info('b')
149 msg = client.get_shell_msg(block=True, timeout=2)
154 150 content = msg['content']
155 151 nt.assert_true(content['found'])
156 152 nt.assert_equal(content['string_form'], u'hi there')
157 153
158 154 # oinfo c (undefined)
159 msg_id = shell.object_info('c')
160 msg = shell.get_msg(block=True, timeout=2)
155 msg_id = client.object_info('c')
156 msg = client.get_shell_msg(block=True, timeout=2)
161 157 content = msg['content']
162 158 nt.assert_false(content['found'])
163 159
@@ -176,18 +172,17 b' def test_embed_kernel_reentrant():'
176 172 '',
177 173 ])
178 174
179 with setup_kernel(cmd) as km:
180 shell = km.shell_channel
175 with setup_kernel(cmd) as client:
181 176 for i in range(5):
182 msg_id = shell.object_info('count')
183 msg = shell.get_msg(block=True, timeout=2)
177 msg_id = client.object_info('count')
178 msg = client.get_shell_msg(block=True, timeout=2)
184 179 content = msg['content']
185 180 nt.assert_true(content['found'])
186 181 nt.assert_equal(content['string_form'], unicode(i))
187 182
188 183 # exit from embed_kernel
189 shell.execute("get_ipython().exit_now = True")
190 msg = shell.get_msg(block=True, timeout=2)
184 client.execute("get_ipython().exit_now = True")
185 msg = client.get_shell_msg(block=True, timeout=2)
191 186 time.sleep(0.2)
192 187
193 188
@@ -195,7 +195,7 b' The ``user_`` fields deserve a detailed explanation. In the past, IPython had'
195 195 the notion of a prompt string that allowed arbitrary code to be evaluated, and
196 196 this was put to good use by many in creating prompts that displayed system
197 197 status, path information, and even more esoteric uses like remote instrument
198 status aqcuired over the network. But now that IPython has a clean separation
198 status acquired over the network. But now that IPython has a clean separation
199 199 between the kernel and the clients, the kernel has no prompt knowledge; prompts
200 200 are a frontend-side feature, and it should be even possible for different
201 201 frontends to display different prompts while interacting with the same kernel.
@@ -934,7 +934,8 b' Message type: ``status``::'
934 934 content = {
935 935 # When the kernel starts to execute code, it will enter the 'busy'
936 936 # state and when it finishes, it will enter the 'idle' state.
937 execution_state : ('busy', 'idle')
937 # The kernel will publish state 'starting' exactly once at process startup.
938 execution_state : ('busy', 'idle', 'starting')
938 939 }
939 940
940 941 Kernel crashes
General Comments 0
You need to be logged in to leave comments. Login now