##// 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 def __init__(self, config=None, ipython_dir=None, profile_dir=None,
422 def __init__(self, config=None, ipython_dir=None, profile_dir=None,
423 user_module=None, user_ns=None,
423 user_module=None, user_ns=None,
424 custom_exceptions=((), None)):
424 custom_exceptions=((), None), **kwargs):
425
425
426 # This is where traits with a config_key argument are updated
426 # This is where traits with a config_key argument are updated
427 # from the values on config.
427 # from the values on config.
428 super(InteractiveShell, self).__init__(config=config)
428 super(InteractiveShell, self).__init__(config=config, **kwargs)
429 self.configurables = [self]
429 self.configurables = [self]
430
430
431 # These are relatively independent and stateless
431 # These are relatively independent and stateless
@@ -34,8 +34,8 b' import uuid'
34 from IPython.config.application import boolean_flag
34 from IPython.config.application import boolean_flag
35 from IPython.config.configurable import Configurable
35 from IPython.config.configurable import Configurable
36 from IPython.core.profiledir import ProfileDir
36 from IPython.core.profiledir import ProfileDir
37 from IPython.kernel.blockingkernelmanager import BlockingKernelManager
37 from IPython.kernel.blocking import BlockingKernelClient
38 from IPython.kernel.kernelmanager import KernelManager
38 from IPython.kernel import KernelManager
39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
40 from IPython.utils.path import filefind
40 from IPython.utils.path import filefind
41 from IPython.utils.py3compat import str_to_bytes
41 from IPython.utils.py3compat import str_to_bytes
@@ -144,7 +144,8 b' class IPythonConsoleApp(Configurable):'
144 classes = classes
144 classes = classes
145 flags = Dict(flags)
145 flags = Dict(flags)
146 aliases = Dict(aliases)
146 aliases = Dict(aliases)
147 kernel_manager_class = BlockingKernelManager
147 kernel_manager_class = KernelManager
148 kernel_client_class = BlockingKernelClient
148
149
149 kernel_argv = List(Unicode)
150 kernel_argv = List(Unicode)
150 # frontend flags&aliases to be stripped when building kernel_argv
151 # frontend flags&aliases to be stripped when building kernel_argv
@@ -328,6 +329,9 b' class IPythonConsoleApp(Configurable):'
328
329
329 def init_kernel_manager(self):
330 def init_kernel_manager(self):
330 # Don't let Qt or ZMQ swallow KeyboardInterupts.
331 # Don't let Qt or ZMQ swallow KeyboardInterupts.
332 if self.existing:
333 self.kernel_manager = None
334 return
331 signal.signal(signal.SIGINT, signal.SIG_DFL)
335 signal.signal(signal.SIGINT, signal.SIG_DFL)
332
336
333 # Create a KernelManager and start a kernel.
337 # Create a KernelManager and start a kernel.
@@ -339,15 +343,39 b' class IPythonConsoleApp(Configurable):'
339 connection_file=self.connection_file,
343 connection_file=self.connection_file,
340 config=self.config,
344 config=self.config,
341 )
345 )
342 # start the kernel
346 self.kernel_manager.client_factory = self.kernel_client_class
343 if not self.existing:
347 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
344 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
348 atexit.register(self.kernel_manager.cleanup_ipc_files)
345 atexit.register(self.kernel_manager.cleanup_ipc_files)
349
346 elif self.sshserver:
350 if self.sshserver:
347 # ssh, write new connection file
351 # ssh, write new connection file
348 self.kernel_manager.write_connection_file()
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 atexit.register(self.kernel_manager.cleanup_connection_file)
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 def initialize(self, argv=None):
381 def initialize(self, argv=None):
@@ -359,4 +387,5 b' class IPythonConsoleApp(Configurable):'
359 default_secure(self.config)
387 default_secure(self.config)
360 self.init_ssh()
388 self.init_ssh()
361 self.init_kernel_manager()
389 self.init_kernel_manager()
390 self.init_kernel_client()
362
391
@@ -407,6 +407,9 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
407 return jsonapi.dumps(msg, default=date_default)
407 return jsonapi.dumps(msg, default=date_default)
408
408
409 def _on_zmq_reply(self, msg_list):
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 try:
413 try:
411 msg = self._reserialize_reply(msg_list)
414 msg = self._reserialize_reply(msg_list)
412 except Exception:
415 except Exception:
@@ -466,10 +469,7 b' class AuthenticatedZMQStreamHandler(ZMQStreamHandler):'
466 class IOPubHandler(AuthenticatedZMQStreamHandler):
469 class IOPubHandler(AuthenticatedZMQStreamHandler):
467
470
468 def initialize(self, *args, **kwargs):
471 def initialize(self, *args, **kwargs):
469 self._kernel_alive = True
470 self._beating = False
471 self.iopub_stream = None
472 self.iopub_stream = None
472 self.hb_stream = None
473
473
474 def on_first_message(self, msg):
474 def on_first_message(self, msg):
475 try:
475 try:
@@ -478,12 +478,11 b' class IOPubHandler(AuthenticatedZMQStreamHandler):'
478 self.close()
478 self.close()
479 return
479 return
480 km = self.application.kernel_manager
480 km = self.application.kernel_manager
481 self.time_to_dead = km.time_to_dead
482 self.first_beat = km.first_beat
483 kernel_id = self.kernel_id
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 try:
484 try:
485 self.iopub_stream = km.create_iopub_stream(kernel_id)
485 self.iopub_stream = km.connect_iopub(kernel_id)
486 self.hb_stream = km.create_hb_stream(kernel_id)
487 except web.HTTPError:
486 except web.HTTPError:
488 # WebSockets don't response to traditional error codes so we
487 # WebSockets don't response to traditional error codes so we
489 # close the connection.
488 # close the connection.
@@ -492,81 +491,39 b' class IOPubHandler(AuthenticatedZMQStreamHandler):'
492 self.close()
491 self.close()
493 else:
492 else:
494 self.iopub_stream.on_recv(self._on_zmq_reply)
493 self.iopub_stream.on_recv(self._on_zmq_reply)
495 self.start_hb(self.kernel_died)
496
494
497 def on_message(self, msg):
495 def on_message(self, msg):
498 pass
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 def on_close(self):
512 def on_close(self):
501 # This method can be called twice, once by self.kernel_died and once
513 # This method can be called twice, once by self.kernel_died and once
502 # from the WebSocket close event. If the WebSocket connection is
514 # from the WebSocket close event. If the WebSocket connection is
503 # closed before the ZMQ streams are setup, they could be None.
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 if self.iopub_stream is not None and not self.iopub_stream.closed():
524 if self.iopub_stream is not None and not self.iopub_stream.closed():
506 self.iopub_stream.on_recv(None)
525 self.iopub_stream.on_recv(None)
507 self.iopub_stream.close()
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 class ShellHandler(AuthenticatedZMQStreamHandler):
529 class ShellHandler(AuthenticatedZMQStreamHandler):
@@ -584,7 +541,7 b' class ShellHandler(AuthenticatedZMQStreamHandler):'
584 self.max_msg_size = km.max_msg_size
541 self.max_msg_size = km.max_msg_size
585 kernel_id = self.kernel_id
542 kernel_id = self.kernel_id
586 try:
543 try:
587 self.shell_stream = km.create_shell_stream(kernel_id)
544 self.shell_stream = km.connect_shell(kernel_id)
588 except web.HTTPError:
545 except web.HTTPError:
589 # WebSockets don't response to traditional error codes so we
546 # WebSockets don't response to traditional error codes so we
590 # close the connection.
547 # close the connection.
@@ -20,8 +20,9 b' from tornado import web'
20
20
21 from IPython.kernel.multikernelmanager import MultiKernelManager
21 from IPython.kernel.multikernelmanager import MultiKernelManager
22 from IPython.utils.traitlets import (
22 from IPython.utils.traitlets import (
23 Dict, List, Unicode, Float, Integer,
23 Dict, List, Unicode, Integer,
24 )
24 )
25
25 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
26 # Classes
27 # Classes
27 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
@@ -30,11 +31,11 b' from IPython.utils.traitlets import ('
30 class MappingKernelManager(MultiKernelManager):
31 class MappingKernelManager(MultiKernelManager):
31 """A KernelManager that handles notebok mapping and HTTP error handling"""
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 kernel_argv = List(Unicode)
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 max_msg_size = Integer(65536, config=True, help="""
39 max_msg_size = Integer(65536, config=True, help="""
39 The max raw message size accepted from the browser
40 The max raw message size accepted from the browser
40 over a WebSocket connection.
41 over a WebSocket connection.
@@ -57,11 +58,10 b' class MappingKernelManager(MultiKernelManager):'
57
58
58 def notebook_for_kernel(self, kernel_id):
59 def notebook_for_kernel(self, kernel_id):
59 """Return the notebook_id for a kernel_id or None."""
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 for notebook_id, kid in self._notebook_mapping.iteritems():
61 if len(notebook_ids) == 1:
62 if kernel_id == kid:
62 return notebook_ids[0]
63 return notebook_id
63 else:
64 return None
64 return None
65
65
66 def delete_mapping_for_kernel(self, kernel_id):
66 def delete_mapping_for_kernel(self, kernel_id):
67 """Remove the kernel/notebook mapping for kernel_id."""
67 """Remove the kernel/notebook mapping for kernel_id."""
@@ -69,8 +69,14 b' class MappingKernelManager(MultiKernelManager):'
69 if notebook_id is not None:
69 if notebook_id is not None:
70 del self._notebook_mapping[notebook_id]
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 def start_kernel(self, notebook_id=None, **kwargs):
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 Parameters
81 Parameters
76 ----------
82 ----------
@@ -86,46 +92,22 b' class MappingKernelManager(MultiKernelManager):'
86 self.set_kernel_for_notebook(notebook_id, kernel_id)
92 self.set_kernel_for_notebook(notebook_id, kernel_id)
87 self.log.info("Kernel started: %s" % kernel_id)
93 self.log.info("Kernel started: %s" % kernel_id)
88 self.log.debug("Kernel args: %r" % kwargs)
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 else:
100 else:
90 self.log.info("Using existing kernel: %s" % kernel_id)
101 self.log.info("Using existing kernel: %s" % kernel_id)
102
91 return kernel_id
103 return kernel_id
92
104
93 def shutdown_kernel(self, kernel_id, now=False):
105 def shutdown_kernel(self, kernel_id, now=False):
94 """Shutdown a kernel and remove its notebook association."""
106 """Shutdown a kernel by kernel_id"""
95 self._check_kernel_id(kernel_id)
107 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
96 super(MappingKernelManager, self).shutdown_kernel(
97 kernel_id, now=now
98 )
99 self.delete_mapping_for_kernel(kernel_id)
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 def _check_kernel_id(self, kernel_id):
111 def _check_kernel_id(self, kernel_id):
130 """Check a that a kernel_id exists and raise 404 if not."""
112 """Check a that a kernel_id exists and raise 404 if not."""
131 if kernel_id not in self:
113 if kernel_id not in self:
@@ -104,8 +104,6 b' var IPython = (function (IPython) {'
104 this.ws_url = json.ws_url;
104 this.ws_url = json.ws_url;
105 this.kernel_url = this.base_url + "/" + this.kernel_id;
105 this.kernel_url = this.base_url + "/" + this.kernel_id;
106 this.start_channels();
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 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
107 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
110 };
108 };
111
109
@@ -165,6 +163,8 b' var IPython = (function (IPython) {'
165 that.iopub_channel.onclose = ws_closed_late;
163 that.iopub_channel.onclose = ws_closed_late;
166 }
164 }
167 }, 1000);
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 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
418 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
419 } else if (content.execution_state === 'idle') {
419 } else if (content.execution_state === 'idle') {
420 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
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 } else if (content.execution_state === 'dead') {
423 } else if (content.execution_state === 'dead') {
422 this.stop_channels();
424 this.stop_channels();
423 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
425 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
@@ -84,7 +84,7 b' var IPython = (function (IPython) {'
84
84
85 $([IPython.events]).on('status_restarting.Kernel',function () {
85 $([IPython.events]).on('status_restarting.Kernel',function () {
86 IPython.save_widget.update_document_title();
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 $([IPython.events]).on('status_interrupting.Kernel',function () {
90 $([IPython.events]).on('status_interrupting.Kernel',function () {
@@ -93,9 +93,10 b' var IPython = (function (IPython) {'
93
93
94 $([IPython.events]).on('status_dead.Kernel',function () {
94 $([IPython.events]).on('status_dead.Kernel',function () {
95 var dialog = $('<div/>');
95 var dialog = $('<div/>');
96 dialog.html('The kernel has died, would you like to restart it?' +
96 dialog.html('The kernel has died, and the automatic restart has failed.' +
97 ' If you do not restart the kernel, you will be able to save' +
97 ' It is possible the kernel cannot be restarted.' +
98 ' the notebook, but running code will not work until the notebook' +
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 ' is reopened.'
100 ' is reopened.'
100 );
101 );
101 $(document).append(dialog);
102 $(document).append(dialog);
@@ -105,7 +106,7 b' var IPython = (function (IPython) {'
105 title: "Dead kernel",
106 title: "Dead kernel",
106 close: function(event, ui) {$(this).dialog('destroy').remove();},
107 close: function(event, ui) {$(this).dialog('destroy').remove();},
107 buttons : {
108 buttons : {
108 "Restart": function () {
109 "Manual Restart": function () {
109 $([IPython.events]).trigger('status_restarting.Kernel');
110 $([IPython.events]).trigger('status_restarting.Kernel');
110 IPython.notebook.start_kernel();
111 IPython.notebook.start_kernel();
111 $(this).dialog('close');
112 $(this).dialog('close');
@@ -12,56 +12,73 b' class BaseFrontendMixin(object):'
12 #---------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13 # 'BaseFrontendMixin' concrete interface
13 # 'BaseFrontendMixin' concrete interface
14 #---------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15
15 _kernel_client = None
16 def _get_kernel_manager(self):
16 _kernel_manager = None
17 """ Returns the current kernel manager.
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
28 # Disconnect the old kernel client, if necessary.
20
29 old_client = self._kernel_client
21 def _set_kernel_manager(self, kernel_manager):
30 if old_client is not None:
22 """ Disconnect from the current kernel manager (if any) and set a new
31 old_client.started_channels.disconnect(self._started_channels)
23 kernel manager.
32 old_client.stopped_channels.disconnect(self._stopped_channels)
24 """
33
25 # Disconnect the old kernel manager, if necessary.
34 # Disconnect the old kernel client's channels.
26 old_manager = self._kernel_manager
35 old_client.iopub_channel.message_received.disconnect(self._dispatch)
27 if old_manager is not None:
36 old_client.shell_channel.message_received.disconnect(self._dispatch)
28 old_manager.started_kernel.disconnect(self._started_kernel)
37 old_client.stdin_channel.message_received.disconnect(self._dispatch)
29 old_manager.started_channels.disconnect(self._started_channels)
38 old_client.hb_channel.kernel_died.disconnect(
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(
37 self._handle_kernel_died)
39 self._handle_kernel_died)
38
40
39 # Handle the case where the old kernel manager is still listening.
41 # Handle the case where the old kernel client is still listening.
40 if old_manager.channels_running:
42 if old_client.channels_running:
41 self._stopped_channels()
43 self._stopped_channels()
42
44
43 # Set the new kernel manager.
45 # Set the new kernel client.
44 self._kernel_manager = kernel_manager
46 self._kernel_client = kernel_client
45 if kernel_manager is None:
47 if kernel_client is None:
46 return
48 return
47
49
48 # Connect the new kernel manager.
50 # Connect the new kernel client.
49 kernel_manager.started_kernel.connect(self._started_kernel)
51 kernel_client.started_channels.connect(self._started_channels)
50 kernel_manager.started_channels.connect(self._started_channels)
52 kernel_client.stopped_channels.connect(self._stopped_channels)
51 kernel_manager.stopped_channels.connect(self._stopped_channels)
52
53
53 # Connect the new kernel manager's channels.
54 # Connect the new kernel client's channels.
54 kernel_manager.iopub_channel.message_received.connect(self._dispatch)
55 kernel_client.iopub_channel.message_received.connect(self._dispatch)
55 kernel_manager.shell_channel.message_received.connect(self._dispatch)
56 kernel_client.shell_channel.message_received.connect(self._dispatch)
56 kernel_manager.stdin_channel.message_received.connect(self._dispatch)
57 kernel_client.stdin_channel.message_received.connect(self._dispatch)
57 kernel_manager.hb_channel.kernel_died.connect(self._handle_kernel_died)
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 # we connected.
62 # we connected.
61 if kernel_manager.channels_running:
63 if kernel_client.channels_running:
62 self._started_channels()
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 # 'BaseFrontendMixin' abstract interface
84 # 'BaseFrontendMixin' abstract interface
@@ -71,8 +88,9 b' class BaseFrontendMixin(object):'
71 """ This is called when the ``kernel_died`` signal is emitted.
88 """ This is called when the ``kernel_died`` signal is emitted.
72
89
73 This method is called when the kernel heartbeat has not been
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
91 active for a certain amount of time.
75 give the user the option of restarting the kernel.
92 This is a strictly passive notification -
93 the kernel is likely being restarted by its KernelManager.
76
94
77 Parameters
95 Parameters
78 ----------
96 ----------
@@ -80,6 +98,17 b' class BaseFrontendMixin(object):'
80 The time since the heartbeat was last received.
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 def _started_kernel(self):
112 def _started_kernel(self):
84 """Called when the KernelManager starts (or restarts) the kernel subprocess.
113 """Called when the KernelManager starts (or restarts) the kernel subprocess.
85 Channels may or may not be running at this point.
114 Channels may or may not be running at this point.
@@ -112,7 +141,7 b' class BaseFrontendMixin(object):'
112 """ Returns whether a reply from the kernel originated from a request
141 """ Returns whether a reply from the kernel originated from a request
113 from this frontend.
142 from this frontend.
114 """
143 """
115 session = self._kernel_manager.session.session
144 session = self._kernel_client.session.session
116 parent = msg['parent_header']
145 parent = msg['parent_header']
117 if not parent:
146 if not parent:
118 # if the message has no parent, assume it is meant for all frontends
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 from IPython.utils.traitlets import Type
5 from IPython.utils.traitlets import Type
6 from IPython.kernel.kernelmanager import ShellChannel, IOPubChannel, \
6 from IPython.kernel.channels import (
7 StdInChannel, HBChannel, KernelManager
7 ShellChannel, IOPubChannel, StdInChannel, HBChannel
8 from base_kernelmanager import QtShellChannelMixin, QtIOPubChannelMixin, \
8 )
9 QtStdInChannelMixin, QtHBChannelMixin, QtKernelManagerMixin
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 class QtShellChannel(QtShellChannelMixin, ShellChannel):
17 class QtShellChannel(QtShellChannelMixin, ShellChannel):
13 pass
18 pass
@@ -22,8 +27,8 b' class QtHBChannel(QtHBChannelMixin, HBChannel):'
22 pass
27 pass
23
28
24
29
25 class QtKernelManager(QtKernelManagerMixin, KernelManager):
30 class QtKernelClient(QtKernelClientMixin, KernelClient):
26 """ A KernelManager that provides signals and slots.
31 """ A KernelClient that provides signals and slots.
27 """
32 """
28
33
29 iopub_channel_class = Type(QtIOPubChannel)
34 iopub_channel_class = Type(QtIOPubChannel)
@@ -148,6 +148,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
148 self._highlighter = FrontendHighlighter(self)
148 self._highlighter = FrontendHighlighter(self)
149 self._input_splitter = self._input_splitter_class()
149 self._input_splitter = self._input_splitter_class()
150 self._kernel_manager = None
150 self._kernel_manager = None
151 self._kernel_client = None
151 self._request_info = {}
152 self._request_info = {}
152 self._request_info['execute'] = {};
153 self._request_info['execute'] = {};
153 self._callback_dict = {}
154 self._callback_dict = {}
@@ -215,7 +216,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
215
216
216 See parent class :meth:`execute` docstring for full details.
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 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
220 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
220 self._hidden = hidden
221 self._hidden = hidden
221 if not hidden:
222 if not hidden:
@@ -357,7 +358,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
357 # generate uuid, which would be used as an indication of whether or
358 # generate uuid, which would be used as an indication of whether or
358 # not the unique request originated from here (can use msg id ?)
359 # not the unique request originated from here (can use msg id ?)
359 local_uuid = str(uuid.uuid1())
360 local_uuid = str(uuid.uuid1())
360 msg_id = self.kernel_manager.shell_channel.execute('',
361 msg_id = self.kernel_client.execute('',
361 silent=True, user_expressions={ local_uuid:expr })
362 silent=True, user_expressions={ local_uuid:expr })
362 self._callback_dict[local_uuid] = callback
363 self._callback_dict[local_uuid] = callback
363 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
364 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
@@ -400,7 +401,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
400 if info and info.kind == 'user' and not self._hidden:
401 if info and info.kind == 'user' and not self._hidden:
401 # Make sure that all output from the SUB channel has been processed
402 # Make sure that all output from the SUB channel has been processed
402 # before writing a new prompt.
403 # before writing a new prompt.
403 self.kernel_manager.iopub_channel.flush()
404 self.kernel_client.iopub_channel.flush()
404
405
405 # Reset the ANSI style information to prevent bad text in stdout
406 # Reset the ANSI style information to prevent bad text in stdout
406 # from messing up our colors. We're not a true terminal so we're
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 # Make sure that all output from the SUB channel has been processed
437 # Make sure that all output from the SUB channel has been processed
437 # before entering readline mode.
438 # before entering readline mode.
438 self.kernel_manager.iopub_channel.flush()
439 self.kernel_client.iopub_channel.flush()
439
440
440 def callback(line):
441 def callback(line):
441 self.kernel_manager.stdin_channel.input(line)
442 self.kernel_client.stdin_channel.input(line)
442 if self._reading:
443 if self._reading:
443 self.log.debug("Got second input request, assuming first was interrupted.")
444 self.log.debug("Got second input request, assuming first was interrupted.")
444 self._reading = False
445 self._reading = False
445 self._readline(msg['content']['prompt'], callback=callback)
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 def _handle_kernel_died(self, since_last_heartbeat):
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 if self.custom_restart:
458 if self.custom_restart:
452 self.custom_restart_kernel_died.emit(since_last_heartbeat)
459 self.custom_restart_kernel_died.emit(since_last_heartbeat)
453 else:
460 else:
454 message = 'The kernel heartbeat has been inactive for %.2f ' \
461 self._kernel_restarted_message(died=True)
455 'seconds. Do you want to restart the kernel? You may ' \
462 self.reset()
456 'first want to check the network connection.' % \
463
457 since_last_heartbeat
464 def _handle_kernel_restarted(self, died=True):
458 self.restart_kernel(message, now=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 def _handle_object_info_reply(self, rep):
473 def _handle_object_info_reply(self, rep):
461 """ Handle replies for call tips.
474 """ Handle replies for call tips.
@@ -505,37 +518,42 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
505 def _handle_shutdown_reply(self, msg):
518 def _handle_shutdown_reply(self, msg):
506 """ Handle shutdown signal, only if from other console.
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 if not self._hidden and not self._is_from_this_session(msg):
523 if not self._hidden and not self._is_from_this_session(msg):
510 if self._local_kernel:
524 # got shutdown reply, request came from session other than ours
511 if not msg['content']['restart']:
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 self.exit_requested.emit(self)
534 self.exit_requested.emit(self)
513 else:
535 else:
514 # we just got notified of a restart!
536 title = self.window().windowTitle()
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']:
522 reply = QtGui.QMessageBox.question(self, title,
537 reply = QtGui.QMessageBox.question(self, title,
523 "Kernel has been shutdown permanently. "
538 "Kernel has been shutdown permanently. "
524 "Close the Console?",
539 "Close the Console?",
525 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
540 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
526 if reply == QtGui.QMessageBox.Yes:
541 if reply == QtGui.QMessageBox.Yes:
527 self.exit_requested.emit(self)
542 self.exit_requested.emit(self)
528 else:
543
529 # XXX: remove message box in favor of using the
544 def _handle_status(self, msg):
530 # clear_on_kernel_restart setting?
545 """Handle status message"""
531 reply = QtGui.QMessageBox.question(self, title,
546 # This is where a busy/idle indicator would be triggered,
532 "Kernel has been reset. Clear the Console?",
547 # when we make one.
533 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
548 state = msg['content'].get('execution_state', '')
534 if reply == QtGui.QMessageBox.Yes:
549 if state == 'starting':
535 time.sleep(0.25) # wait 1/4 sec to reset
550 # kernel started while we were running
536 # lest the request for a new prompt
551 if self._executing:
537 # goes to the old kernel
552 self._handle_kernel_restarted(died=True)
538 self.reset()
553 elif state == 'idle':
554 pass
555 elif state == 'busy':
556 pass
539
557
540 def _started_channels(self):
558 def _started_channels(self):
541 """ Called when the KernelManager channels have started listening or
559 """ Called when the KernelManager channels have started listening or
@@ -568,16 +586,15 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
568 if self.custom_interrupt:
586 if self.custom_interrupt:
569 self._reading = False
587 self._reading = False
570 self.custom_interrupt_requested.emit()
588 self.custom_interrupt_requested.emit()
571 elif self.kernel_manager.has_kernel:
589 elif self.kernel_manager:
572 self._reading = False
590 self._reading = False
573 self.kernel_manager.interrupt_kernel()
591 self.kernel_manager.interrupt_kernel()
574 else:
592 else:
575 self._append_plain_text('Kernel process is either remote or '
593 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
576 'unspecified. Cannot interrupt.\n')
577
594
578 def reset(self, clear=False):
595 def reset(self, clear=False):
579 """ Resets the widget to its initial state if ``clear`` parameter or
596 """ Resets the widget to its initial state if ``clear`` parameter
580 ``clear_on_kernel_restart`` configuration setting is True, otherwise
597 is True, otherwise
581 prints a visual indication of the fact that the kernel restarted, but
598 prints a visual indication of the fact that the kernel restarted, but
582 does not clear the traces from previous usage of the kernel before it
599 does not clear the traces from previous usage of the kernel before it
583 was restarted. With ``clear=True``, it is similar to ``%clear``, but
600 was restarted. With ``clear=True``, it is similar to ``%clear``, but
@@ -589,15 +606,9 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
589 self._reading = False
606 self._reading = False
590 self._highlighter.highlighting_on = False
607 self._highlighter.highlighting_on = False
591
608
592 if self.clear_on_kernel_restart or clear:
609 if clear:
593 self._control.clear()
610 self._control.clear()
594 self._append_plain_text(self.banner)
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 # update output marker for stdout/stderr, so that startup
612 # update output marker for stdout/stderr, so that startup
602 # messages appear after banner:
613 # messages appear after banner:
603 self._append_before_prompt_pos = self._get_cursor().position()
614 self._append_before_prompt_pos = self._get_cursor().position()
@@ -614,10 +625,11 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
614
625
615 if self.custom_restart:
626 if self.custom_restart:
616 self.custom_restart_requested.emit()
627 self.custom_restart_requested.emit()
628 return
617
629
618 elif self.kernel_manager.has_kernel:
630 if self.kernel_manager:
619 # Pause the heart beat channel to prevent further warnings.
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 # Prompt the user to restart the kernel. Un-pause the heartbeat if
634 # Prompt the user to restart the kernel. Un-pause the heartbeat if
623 # they decline. (If they accept, the heartbeat will be un-paused
635 # they decline. (If they accept, the heartbeat will be un-paused
@@ -634,21 +646,23 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
634 if do_restart:
646 if do_restart:
635 try:
647 try:
636 self.kernel_manager.restart_kernel(now=now)
648 self.kernel_manager.restart_kernel(now=now)
637 except RuntimeError:
649 except RuntimeError as e:
638 self._append_plain_text('Kernel started externally. '
650 self._append_plain_text(
639 'Cannot restart.\n',
651 'Error restarting kernel: %s\n' % e,
640 before_prompt=True
652 before_prompt=True
641 )
653 )
642 else:
654 else:
643 self.reset()
655 self._append_html("<br>Restarting kernel...\n<hr><br>",
656 before_prompt=True,
657 )
644 else:
658 else:
645 self.kernel_manager.hb_channel.unpause()
659 self.kernel_client.hb_channel.unpause()
646
660
647 else:
661 else:
648 self._append_plain_text('Kernel process is either remote or '
662 self._append_plain_text(
649 'unspecified. Cannot restart.\n',
663 'Cannot restart a Kernel I did not start\n',
650 before_prompt=True
664 before_prompt=True
651 )
665 )
652
666
653 #---------------------------------------------------------------------------
667 #---------------------------------------------------------------------------
654 # 'FrontendWidget' protected interface
668 # 'FrontendWidget' protected interface
@@ -670,7 +684,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
670
684
671 # Send the metadata request to the kernel
685 # Send the metadata request to the kernel
672 name = '.'.join(context)
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 pos = self._get_cursor().position()
688 pos = self._get_cursor().position()
675 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
689 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
676 return True
690 return True
@@ -681,7 +695,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
681 context = self._get_context()
695 context = self._get_context()
682 if context:
696 if context:
683 # Send the completion request to the kernel
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 '.'.join(context), # text
699 '.'.join(context), # text
686 self._get_input_buffer_cursor_line(), # line
700 self._get_input_buffer_cursor_line(), # line
687 self._get_input_buffer_cursor_column(), # cursor_pos
701 self._get_input_buffer_cursor_column(), # cursor_pos
@@ -224,7 +224,7 b' class HistoryConsoleWidget(ConsoleWidget):'
224 return self._history[-n:]
224 return self._history[-n:]
225
225
226 def _request_update_session_history_length(self):
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 silent=True,
228 silent=True,
229 user_expressions={
229 user_expressions={
230 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
230 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
@@ -194,7 +194,7 b' class IPythonWidget(FrontendWidget):'
194 self._retrying_history_request = True
194 self._retrying_history_request = True
195 # wait out the kernel's queue flush, which is currently timed at 0.1s
195 # wait out the kernel's queue flush, which is currently timed at 0.1s
196 time.sleep(0.25)
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 else:
198 else:
199 self._retrying_history_request = False
199 self._retrying_history_request = False
200 return
200 return
@@ -261,7 +261,7 b' class IPythonWidget(FrontendWidget):'
261 """Reimplemented to make a history request and load %guiref."""
261 """Reimplemented to make a history request and load %guiref."""
262 super(IPythonWidget, self)._started_channels()
262 super(IPythonWidget, self)._started_channels()
263 self._load_guiref_magic()
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 n=1000)
265 n=1000)
266
266
267 def _started_kernel(self):
267 def _started_kernel(self):
@@ -269,12 +269,12 b' class IPythonWidget(FrontendWidget):'
269
269
270 Principally triggered by kernel restart.
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 self._load_guiref_magic()
273 self._load_guiref_magic()
274
274
275 def _load_guiref_magic(self):
275 def _load_guiref_magic(self):
276 """Load %guiref magic."""
276 """Load %guiref magic."""
277 self.kernel_manager.shell_channel.execute('\n'.join([
277 self.kernel_client.shell_channel.execute('\n'.join([
278 "try:",
278 "try:",
279 " _usage",
279 " _usage",
280 "except:",
280 "except:",
@@ -330,7 +330,7 b' class IPythonWidget(FrontendWidget):'
330 text = ''
330 text = ''
331
331
332 # Send the completion request to the kernel
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 text, # text
334 text, # text
335 self._get_input_buffer_cursor_line(), # line
335 self._get_input_buffer_cursor_line(), # line
336 self._get_input_buffer_cursor_column(), # cursor_pos
336 self._get_input_buffer_cursor_column(), # cursor_pos
@@ -376,7 +376,7 b' class IPythonWidget(FrontendWidget):'
376 """
376 """
377 # If a number was not specified, make a prompt number request.
377 # If a number was not specified, make a prompt number request.
378 if number is None:
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 info = self._ExecutionRequest(msg_id, 'prompt')
380 info = self._ExecutionRequest(msg_id, 'prompt')
381 self._request_info['execute'][msg_id] = info
381 self._request_info['execute'][msg_id] = info
382 return
382 return
@@ -176,6 +176,7 b' class MainWindow(QtGui.QMainWindow):'
176 self.update_tab_bar_visibility()
176 self.update_tab_bar_visibility()
177 return
177 return
178
178
179 kernel_client = closing_widget.kernel_client
179 kernel_manager = closing_widget.kernel_manager
180 kernel_manager = closing_widget.kernel_manager
180
181
181 if keepkernel is None and not closing_widget._confirm_exit:
182 if keepkernel is None and not closing_widget._confirm_exit:
@@ -183,7 +184,7 b' class MainWindow(QtGui.QMainWindow):'
183 # or leave it alone if we don't
184 # or leave it alone if we don't
184 keepkernel = closing_widget._existing
185 keepkernel = closing_widget._existing
185 if keepkernel is None: #show prompt
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 title = self.window().windowTitle()
188 title = self.window().windowTitle()
188 cancel = QtGui.QMessageBox.Cancel
189 cancel = QtGui.QMessageBox.Cancel
189 okay = QtGui.QMessageBox.Ok
190 okay = QtGui.QMessageBox.Ok
@@ -209,17 +210,17 b' class MainWindow(QtGui.QMainWindow):'
209 reply = box.exec_()
210 reply = box.exec_()
210 if reply == 1: # close All
211 if reply == 1: # close All
211 for slave in slave_tabs:
212 for slave in slave_tabs:
212 background(slave.kernel_manager.stop_channels)
213 background(slave.kernel_client.stop_channels)
213 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
214 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
214 closing_widget.execute("exit")
215 closing_widget.execute("exit")
215 self.tab_widget.removeTab(current_tab)
216 self.tab_widget.removeTab(current_tab)
216 background(kernel_manager.stop_channels)
217 background(kernel_client.stop_channels)
217 elif reply == 0: # close Console
218 elif reply == 0: # close Console
218 if not closing_widget._existing:
219 if not closing_widget._existing:
219 # Have kernel: don't quit, just close the tab
220 # Have kernel: don't quit, just close the tab
220 closing_widget.execute("exit True")
221 closing_widget.execute("exit True")
221 self.tab_widget.removeTab(current_tab)
222 self.tab_widget.removeTab(current_tab)
222 background(kernel_manager.stop_channels)
223 background(kernel_client.stop_channels)
223 else:
224 else:
224 reply = QtGui.QMessageBox.question(self, title,
225 reply = QtGui.QMessageBox.question(self, title,
225 "Are you sure you want to close this Console?"+
226 "Are you sure you want to close this Console?"+
@@ -231,15 +232,16 b' class MainWindow(QtGui.QMainWindow):'
231 self.tab_widget.removeTab(current_tab)
232 self.tab_widget.removeTab(current_tab)
232 elif keepkernel: #close console but leave kernel running (no prompt)
233 elif keepkernel: #close console but leave kernel running (no prompt)
233 self.tab_widget.removeTab(current_tab)
234 self.tab_widget.removeTab(current_tab)
234 background(kernel_manager.stop_channels)
235 background(kernel_client.stop_channels)
235 else: #close console and kernel (no prompt)
236 else: #close console and kernel (no prompt)
236 self.tab_widget.removeTab(current_tab)
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 for slave in slave_tabs:
239 for slave in slave_tabs:
239 background(slave.kernel_manager.stop_channels)
240 background(slave.kernel_client.stop_channels)
240 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
241 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
241 kernel_manager.shutdown_kernel()
242 if kernel_manager:
242 background(kernel_manager.stop_channels)
243 kernel_manager.shutdown_kernel()
244 background(kernel_client.stop_channels)
243
245
244 self.update_tab_bar_visibility()
246 self.update_tab_bar_visibility()
245
247
@@ -284,7 +286,7 b' class MainWindow(QtGui.QMainWindow):'
284 #convert from/to int/richIpythonWidget if needed
286 #convert from/to int/richIpythonWidget if needed
285 if isinstance(tab, int):
287 if isinstance(tab, int):
286 tab = self.tab_widget.widget(tab)
288 tab = self.tab_widget.widget(tab)
287 km=tab.kernel_manager
289 km=tab.kernel_client
288
290
289 #build list of all widgets
291 #build list of all widgets
290 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
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 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
294 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
293 # And should have a _may_close attribute
295 # And should have a _may_close attribute
294 filtered_widget_list = [ widget for widget in widget_list if
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 hasattr(widget,'_may_close') ]
298 hasattr(widget,'_may_close') ]
297 # the master widget is the one that may close the kernel
299 # the master widget is the one that may close the kernel
298 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
300 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
@@ -315,14 +317,14 b' class MainWindow(QtGui.QMainWindow):'
315 #convert from/to int/richIpythonWidget if needed
317 #convert from/to int/richIpythonWidget if needed
316 if isinstance(tab, int):
318 if isinstance(tab, int):
317 tab = self.tab_widget.widget(tab)
319 tab = self.tab_widget.widget(tab)
318 km=tab.kernel_manager
320 km=tab.kernel_client
319
321
320 #build list of all widgets
322 #build list of all widgets
321 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
323 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
322
324
323 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
325 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
324 filtered_widget_list = ( widget for widget in widget_list if
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 # Get a list of all widget owning the same kernel and removed it from
328 # Get a list of all widget owning the same kernel and removed it from
327 # the previous cadidate. (better using sets ?)
329 # the previous cadidate. (better using sets ?)
328 master_widget_list = self.find_master_tab(tab, as_list=True)
330 master_widget_list = self.find_master_tab(tab, as_list=True)
@@ -20,11 +20,9 b' Authors:'
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 # stdlib imports
22 # stdlib imports
23 import json
24 import os
23 import os
25 import signal
24 import signal
26 import sys
25 import sys
27 import uuid
28
26
29 # If run on Windows, install an exception hook which pops up a
27 # If run on Windows, install an exception hook which pops up a
30 # message box. Pythonw.exe hides the console, so without this
28 # message box. Pythonw.exe hides the console, so without this
@@ -59,21 +57,17 b' from IPython.external.qt import QtCore, QtGui'
59 from IPython.config.application import boolean_flag, catch_config_error
57 from IPython.config.application import boolean_flag, catch_config_error
60 from IPython.core.application import BaseIPythonApplication
58 from IPython.core.application import BaseIPythonApplication
61 from IPython.core.profiledir import ProfileDir
59 from IPython.core.profiledir import ProfileDir
62 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
63 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
60 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
64 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
61 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
65 from IPython.frontend.qt.console import styles
62 from IPython.frontend.qt.console import styles
66 from IPython.frontend.qt.console.mainwindow import MainWindow
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 from IPython.kernel import tunnel_to_kernel, find_connection_file
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 from IPython.utils.traitlets import (
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
70 from IPython.kernel.zmq.session import default_secure
75 from IPython.kernel.zmq.session import Session, default_secure
76 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
77
71
78 from IPython.frontend.consoleapp import (
72 from IPython.frontend.consoleapp import (
79 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
73 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
@@ -166,6 +160,7 b' class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):'
166 aliases = Dict(aliases)
160 aliases = Dict(aliases)
167 frontend_flags = Any(qt_flags)
161 frontend_flags = Any(qt_flags)
168 frontend_aliases = Any(qt_aliases)
162 frontend_aliases = Any(qt_aliases)
163 kernel_client_class = QtKernelClient
169 kernel_manager_class = QtKernelManager
164 kernel_manager_class = QtKernelManager
170
165
171 stylesheet = Unicode('', config=True,
166 stylesheet = Unicode('', config=True,
@@ -196,16 +191,20 b' class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):'
196 kernel_manager = self.kernel_manager_class(
191 kernel_manager = self.kernel_manager_class(
197 connection_file=self._new_connection_file(),
192 connection_file=self._new_connection_file(),
198 config=self.config,
193 config=self.config,
194 autorestart=True,
199 )
195 )
200 # start the kernel
196 # start the kernel
201 kwargs = dict()
197 kwargs = dict()
202 kwargs['extra_arguments'] = self.kernel_argv
198 kwargs['extra_arguments'] = self.kernel_argv
203 kernel_manager.start_kernel(**kwargs)
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 widget = self.widget_factory(config=self.config,
203 widget = self.widget_factory(config=self.config,
206 local_kernel=True)
204 local_kernel=True)
207 self.init_colors(widget)
205 self.init_colors(widget)
208 widget.kernel_manager = kernel_manager
206 widget.kernel_manager = kernel_manager
207 widget.kernel_client = kernel_client
209 widget._existing = False
208 widget._existing = False
210 widget._may_close = True
209 widget._may_close = True
211 widget._confirm_exit = self.confirm_exit
210 widget._confirm_exit = self.confirm_exit
@@ -219,24 +218,28 b' class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):'
219 current_widget : IPythonWidget
218 current_widget : IPythonWidget
220 The IPythonWidget whose kernel this frontend is to share
219 The IPythonWidget whose kernel this frontend is to share
221 """
220 """
222 kernel_manager = self.kernel_manager_class(
221 kernel_client = self.kernel_client_class(
223 connection_file=current_widget.kernel_manager.connection_file,
222 connection_file=current_widget.kernel_client.connection_file,
224 config = self.config,
223 config = self.config,
225 )
224 )
226 kernel_manager.load_connection_file()
225 kernel_client.load_connection_file()
227 kernel_manager.start_channels()
226 kernel_client.start_channels()
228 widget = self.widget_factory(config=self.config,
227 widget = self.widget_factory(config=self.config,
229 local_kernel=False)
228 local_kernel=False)
230 self.init_colors(widget)
229 self.init_colors(widget)
231 widget._existing = True
230 widget._existing = True
232 widget._may_close = False
231 widget._may_close = False
233 widget._confirm_exit = False
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 return widget
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 def init_qt_elements(self):
241 def init_qt_elements(self):
238 # Create the widget.
242 # Create the widget.
239 self.app = QtGui.QApplication([])
240
243
241 base_path = os.path.abspath(os.path.dirname(__file__))
244 base_path = os.path.abspath(os.path.dirname(__file__))
242 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
245 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
@@ -256,6 +259,7 b' class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):'
256 self.widget._confirm_exit = self.confirm_exit
259 self.widget._confirm_exit = self.confirm_exit
257
260
258 self.widget.kernel_manager = self.kernel_manager
261 self.widget.kernel_manager = self.kernel_manager
262 self.widget.kernel_client = self.kernel_client
259 self.window = MainWindow(self.app,
263 self.window = MainWindow(self.app,
260 confirm_exit=self.confirm_exit,
264 confirm_exit=self.confirm_exit,
261 new_frontend_factory=self.new_frontend_master,
265 new_frontend_factory=self.new_frontend_master,
@@ -342,6 +346,7 b' class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):'
342
346
343 @catch_config_error
347 @catch_config_error
344 def initialize(self, argv=None):
348 def initialize(self, argv=None):
349 self.init_qt_app()
345 super(IPythonQtConsoleApp, self).initialize(argv)
350 super(IPythonQtConsoleApp, self).initialize(argv)
346 IPythonConsoleApp.initialize(self,argv)
351 IPythonConsoleApp.initialize(self,argv)
347 self.init_qt_elements()
352 self.init_qt_elements()
@@ -2,12 +2,17 b''
2 """
2 """
3
3
4 # Local imports.
4 # Local imports.
5 from IPython.kernel.inprocess.kernelmanager import \
5 from IPython.kernel.inprocess import (
6 InProcessShellChannel, InProcessIOPubChannel, InProcessStdInChannel, \
6 InProcessShellChannel, InProcessIOPubChannel, InProcessStdInChannel,
7 InProcessHBChannel, InProcessKernelManager
7 InProcessHBChannel, InProcessKernelClient, InProcessKernelManager,
8 )
9
8 from IPython.utils.traitlets import Type
10 from IPython.utils.traitlets import Type
9 from base_kernelmanager import QtShellChannelMixin, QtIOPubChannelMixin, \
11 from .kernel_mixins import (
10 QtStdInChannelMixin, QtHBChannelMixin, QtKernelManagerMixin
12 QtShellChannelMixin, QtIOPubChannelMixin,
13 QtStdInChannelMixin, QtHBChannelMixin, QtKernelClientMixin,
14 QtKernelManagerMixin,
15 )
11
16
12
17
13 class QtInProcessShellChannel(QtShellChannelMixin, InProcessShellChannel):
18 class QtInProcessShellChannel(QtShellChannelMixin, InProcessShellChannel):
@@ -22,8 +27,7 b' class QtInProcessStdInChannel(QtStdInChannelMixin, InProcessStdInChannel):'
22 class QtInProcessHBChannel(QtHBChannelMixin, InProcessHBChannel):
27 class QtInProcessHBChannel(QtHBChannelMixin, InProcessHBChannel):
23 pass
28 pass
24
29
25
30 class QtInProcessKernelClient(QtKernelClientMixin, InProcessKernelClient):
26 class QtInProcessKernelManager(QtKernelManagerMixin, InProcessKernelManager):
27 """ An in-process KernelManager with signals and slots.
31 """ An in-process KernelManager with signals and slots.
28 """
32 """
29
33
@@ -31,3 +35,6 b' class QtInProcessKernelManager(QtKernelManagerMixin, InProcessKernelManager):'
31 shell_channel_class = Type(QtInProcessShellChannel)
35 shell_channel_class = Type(QtInProcessShellChannel)
32 stdin_channel_class = Type(QtInProcessStdInChannel)
36 stdin_channel_class = Type(QtInProcessStdInChannel)
33 hb_channel_class = Type(QtInProcessHBChannel)
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 # Emitted when any message is received.
54 # Emitted when any message is received.
55 message_received = QtCore.Signal(object)
55 message_received = QtCore.Signal(object)
56
56
57 # Emitted when a reply has been received for the corresponding request
57 # Emitted when a reply has been received for the corresponding request type.
58 # type.
59 execute_reply = QtCore.Signal(object)
58 execute_reply = QtCore.Signal(object)
60 complete_reply = QtCore.Signal(object)
59 complete_reply = QtCore.Signal(object)
61 object_info_reply = QtCore.Signal(object)
60 object_info_reply = QtCore.Signal(object)
62 history_reply = QtCore.Signal(object)
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 # 'ShellChannel' interface
64 # 'ShellChannel' interface
73 #---------------------------------------------------------------------------
65 #---------------------------------------------------------------------------
@@ -84,19 +76,6 b' class QtShellChannelMixin(ChannelQObject):'
84 if signal:
76 if signal:
85 signal.emit(msg)
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 class QtIOPubChannelMixin(ChannelQObject):
80 class QtIOPubChannelMixin(ChannelQObject):
102
81
@@ -189,19 +168,31 b' class QtHBChannelMixin(ChannelQObject):'
189 self.kernel_died.emit(since_last_heartbeat)
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 class QtKernelManagerMixin(HasTraits, SuperQObject):
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 __metaclass__ = MetaQObjectHasTraits
181 __metaclass__ = MetaQObjectHasTraits
197
182
198 # Emitted when the kernel manager has started listening.
183 kernel_restarted = QtCore.Signal()
199 started_kernel = 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 started_channels = QtCore.Signal()
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 stopped_channels = QtCore.Signal()
196 stopped_channels = QtCore.Signal()
206
197
207 # Use Qt-specific channel classes that emit signals.
198 # Use Qt-specific channel classes that emit signals.
@@ -211,50 +202,19 b' class QtKernelManagerMixin(HasTraits, SuperQObject):'
211 hb_channel_class = Type(QtHBChannelMixin)
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 #------ Channel management -------------------------------------------------
208 #------ Channel management -------------------------------------------------
228
209
229 def start_channels(self, *args, **kw):
210 def start_channels(self, *args, **kw):
230 """ Reimplemented to emit signal.
211 """ Reimplemented to emit signal.
231 """
212 """
232 super(QtKernelManagerMixin, self).start_channels(*args, **kw)
213 super(QtKernelClientMixin, self).start_channels(*args, **kw)
233 self.started_channels.emit()
214 self.started_channels.emit()
234
215
235 def stop_channels(self):
216 def stop_channels(self):
236 """ Reimplemented to emit signal.
217 """ Reimplemented to emit signal.
237 """
218 """
238 super(QtKernelManagerMixin, self).stop_channels()
219 super(QtKernelClientMixin, self).stop_channels()
239 self.stopped_channels.emit()
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 signal.signal(signal.SIGINT, self.handle_sigint)
114 signal.signal(signal.SIGINT, self.handle_sigint)
115 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
115 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
116 display_banner=False, profile_dir=self.profile_dir,
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 def init_gui_pylab(self):
122 def init_gui_pylab(self):
120 # no-op, because we don't want to import matplotlib in the frontend.
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 def handle_sigint(self, *args):
126 def handle_sigint(self, *args):
124 if self.shell._executing:
127 if self.shell._executing:
125 if self.kernel_manager.has_kernel:
128 if self.kernel_manager:
126 # interrupt already gets passed to subprocess by signal handler.
129 # interrupt already gets passed to subprocess by signal handler.
127 # Only if we prevent that should we need to explicitly call
130 # Only if we prevent that should we need to explicitly call
128 # interrupt_kernel, until which time, this would result in a
131 # interrupt_kernel, until which time, this would result in a
@@ -9,9 +9,9 b' class ZMQCompleter(object):'
9 state=0,1,2,... When state=0 it should compute ALL the completion matches,
9 state=0,1,2,... When state=0 it should compute ALL the completion matches,
10 and then return them for each value of state."""
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 self.shell = shell
13 self.shell = shell
14 self.km = km
14 self.client = client
15 self.matches = []
15 self.matches = []
16
16
17 def complete_request(self,text):
17 def complete_request(self,text):
@@ -20,10 +20,10 b' class ZMQCompleter(object):'
20
20
21 # send completion request to kernel
21 # send completion request to kernel
22 # Give the kernel up to 0.5s to respond
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 cursor_pos=cursor_pos)
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 if msg['parent_header']['msg_id'] == msg_id:
27 if msg['parent_header']['msg_id'] == msg_id:
28 return msg["content"]["matches"]
28 return msg["content"]["matches"]
29 return []
29 return []
@@ -1,12 +1,9 b''
1 # -*- coding: utf-8 -*-
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 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
9 # the file COPYING, distributed as part of this software.
@@ -37,7 +34,7 b' from IPython.core.alias import AliasManager, AliasError'
37 from IPython.core import page
34 from IPython.core import page
38 from IPython.utils.warn import warn, error, fatal
35 from IPython.utils.warn import warn, error, fatal
39 from IPython.utils import io
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 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
38 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
42
39
43 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
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):
105 manager = Instance('IPython.kernel.KernelManager')
109 self.km = kwargs.pop('kernel_manager')
106 client = Instance('IPython.kernel.KernelClient')
110 self.session_id = self.km.session.session
107 def _client_changed(self, name, old, new):
111 super(ZMQTerminalInteractiveShell, self).__init__(*args, **kwargs)
108 self.session_id = new.session.session
112
109 session_id = Unicode()
110
113 def init_completer(self):
111 def init_completer(self):
114 """Initialize the completion machinery.
112 """Initialize the completion machinery.
115
113
@@ -121,7 +119,7 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
121 from IPython.core.completerlib import (module_completer,
119 from IPython.core.completerlib import (module_completer,
122 magic_run_completer, cd_completer)
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 self.set_hook('complete_command', module_completer, str_key = 'import')
125 self.set_hook('complete_command', module_completer, str_key = 'import')
@@ -156,18 +154,18 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
156
154
157 self._executing = True
155 self._executing = True
158 # flush stale replies, which could have been ignored, due to missed heartbeats
156 # flush stale replies, which could have been ignored, due to missed heartbeats
159 while self.km.shell_channel.msg_ready():
157 while self.client.shell_channel.msg_ready():
160 self.km.shell_channel.get_msg()
158 self.client.shell_channel.get_msg()
161 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
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)
160 msg_id = self.client.shell_channel.execute(cell, not store_history)
163 while not self.km.shell_channel.msg_ready() and self.km.is_alive:
161 while not self.client.shell_channel.msg_ready() and self.client.is_alive():
164 try:
162 try:
165 self.handle_stdin_request(timeout=0.05)
163 self.handle_stdin_request(timeout=0.05)
166 except Empty:
164 except Empty:
167 # display intermediate print statements, etc.
165 # display intermediate print statements, etc.
168 self.handle_iopub()
166 self.handle_iopub()
169 pass
167 pass
170 if self.km.shell_channel.msg_ready():
168 if self.client.shell_channel.msg_ready():
171 self.handle_execute_reply(msg_id)
169 self.handle_execute_reply(msg_id)
172 self._executing = False
170 self._executing = False
173
171
@@ -176,7 +174,7 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
176 #-----------------
174 #-----------------
177
175
178 def handle_execute_reply(self, msg_id):
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 if msg["parent_header"].get("msg_id", None) == msg_id:
178 if msg["parent_header"].get("msg_id", None) == msg_id:
181
179
182 self.handle_iopub()
180 self.handle_iopub()
@@ -211,8 +209,8 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
211 sub_msg: message receive from kernel in the sub socket channel
209 sub_msg: message receive from kernel in the sub socket channel
212 capture by kernel manager.
210 capture by kernel manager.
213 """
211 """
214 while self.km.iopub_channel.msg_ready():
212 while self.client.iopub_channel.msg_ready():
215 sub_msg = self.km.iopub_channel.get_msg()
213 sub_msg = self.client.iopub_channel.get_msg()
216 msg_type = sub_msg['header']['msg_type']
214 msg_type = sub_msg['header']['msg_type']
217 parent = sub_msg["parent_header"]
215 parent = sub_msg["parent_header"]
218 if (not parent) or self.session_id == parent['session']:
216 if (not parent) or self.session_id == parent['session']:
@@ -298,7 +296,7 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
298 def handle_stdin_request(self, timeout=0.1):
296 def handle_stdin_request(self, timeout=0.1):
299 """ Method to capture raw_input
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 # in case any iopub came while we were waiting:
300 # in case any iopub came while we were waiting:
303 self.handle_iopub()
301 self.handle_iopub()
304 if self.session_id == msg_rep["parent_header"].get("session"):
302 if self.session_id == msg_rep["parent_header"].get("session"):
@@ -325,8 +323,8 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
325
323
326 # only send stdin reply if there *was not* another request
324 # only send stdin reply if there *was not* another request
327 # or execution finished while we were reading.
325 # or execution finished while we were reading.
328 if not (self.km.stdin_channel.msg_ready() or self.km.shell_channel.msg_ready()):
326 if not (self.client.stdin_channel.msg_ready() or self.client.shell_channel.msg_ready()):
329 self.km.stdin_channel.input(raw_data)
327 self.client.stdin_channel.input(raw_data)
330
328
331 def mainloop(self, display_banner=False):
329 def mainloop(self, display_banner=False):
332 while True:
330 while True:
@@ -344,10 +342,10 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
344 def wait_for_kernel(self, timeout=None):
342 def wait_for_kernel(self, timeout=None):
345 """method to wait for a kernel to be ready"""
343 """method to wait for a kernel to be ready"""
346 tic = time.time()
344 tic = time.time()
347 self.km.hb_channel.unpause()
345 self.client.hb_channel.unpause()
348 while True:
346 while True:
349 self.run_cell('1', False)
347 self.run_cell('1', False)
350 if self.km.hb_channel.is_beating():
348 if self.client.hb_channel.is_beating():
351 # heart failure was not the reason this returned
349 # heart failure was not the reason this returned
352 break
350 break
353 else:
351 else:
@@ -389,13 +387,14 b' class ZMQTerminalInteractiveShell(TerminalInteractiveShell):'
389 # ask_exit callback.
387 # ask_exit callback.
390
388
391 while not self.exit_now:
389 while not self.exit_now:
392 if not self.km.is_alive:
390 if not self.client.is_alive():
393 # kernel died, prompt for action or exit
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 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
394 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
396 if ans:
395 if ans:
397 if self.km.has_kernel:
396 if self.manager:
398 self.km.restart_kernel(True)
397 self.manager.restart_kernel(True)
399 self.wait_for_kernel(3)
398 self.wait_for_kernel(3)
400 else:
399 else:
401 self.exit_now = True
400 self.exit_now = True
@@ -10,7 +10,7 b' import sys'
10 import unittest
10 import unittest
11 import base64
11 import base64
12
12
13 from IPython.kernel.kernelmanager import KernelManager
13 from IPython.kernel import KernelClient
14 from IPython.frontend.terminal.console.interactiveshell \
14 from IPython.frontend.terminal.console.interactiveshell \
15 import ZMQTerminalInteractiveShell
15 import ZMQTerminalInteractiveShell
16 from IPython.utils.tempdir import TemporaryDirectory
16 from IPython.utils.tempdir import TemporaryDirectory
@@ -26,8 +26,8 b' SCRIPT_PATH = os.path.join('
26 class ZMQTerminalInteractiveShellTestCase(unittest.TestCase):
26 class ZMQTerminalInteractiveShellTestCase(unittest.TestCase):
27
27
28 def setUp(self):
28 def setUp(self):
29 km = KernelManager()
29 client = KernelClient()
30 self.shell = ZMQTerminalInteractiveShell(kernel_manager=km)
30 self.shell = ZMQTerminalInteractiveShell(kernel_client=client)
31 self.raw = b'dummy data'
31 self.raw = b'dummy data'
32 self.mime = 'image/png'
32 self.mime = 'image/png'
33 self.data = {self.mime: base64.encodestring(self.raw).decode('ascii')}
33 self.data = {self.mime: base64.encodestring(self.raw).decode('ascii')}
@@ -368,11 +368,13 b' class TerminalInteractiveShell(InteractiveShell):'
368
368
369 def __init__(self, config=None, ipython_dir=None, profile_dir=None,
369 def __init__(self, config=None, ipython_dir=None, profile_dir=None,
370 user_ns=None, user_module=None, custom_exceptions=((),None),
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 super(TerminalInteractiveShell, self).__init__(
374 super(TerminalInteractiveShell, self).__init__(
374 config=config, ipython_dir=ipython_dir, profile_dir=profile_dir, user_ns=user_ns,
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 # use os.system instead of utils.process.system by default,
379 # use os.system instead of utils.process.system by default,
378 # because piped system doesn't make sense in the Terminal:
380 # because piped system doesn't make sense in the Terminal:
@@ -5,6 +5,7 b' from . import zmq'
5
5
6 from .connect import *
6 from .connect import *
7 from .launcher import *
7 from .launcher import *
8 from .kernelmanager import KernelManager
8 from .client import KernelClient
9 from .blockingkernelmanager import BlockingKernelManager
9 from .manager import KernelManager
10 from .blocking import BlockingKernelClient
10 from .multikernelmanager import MultiKernelManager
11 from .multikernelmanager import MultiKernelManager
@@ -1,9 +1,9 b''
1 """ Implements a fully blocking kernel manager.
1 """Blocking channels
2
2
3 Useful for test suites and blocking terminal interfaces.
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 # Distributed under the terms of the BSD License. The full license is in
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.
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 import Queue
16 import Queue
17
17
18 from IPython.utils.traitlets import Type
18 from IPython.kernel.channels import IOPubChannel, HBChannel, \
19 from .kernelmanager import KernelManager, IOPubChannel, HBChannel, \
20 ShellChannel, StdInChannel
19 ShellChannel, StdInChannel
21
20
22 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
@@ -25,14 +24,14 b' from .kernelmanager import KernelManager, IOPubChannel, HBChannel, \\'
25
24
26
25
27 class BlockingChannelMixin(object):
26 class BlockingChannelMixin(object):
28
27
29 def __init__(self, *args, **kwds):
28 def __init__(self, *args, **kwds):
30 super(BlockingChannelMixin, self).__init__(*args, **kwds)
29 super(BlockingChannelMixin, self).__init__(*args, **kwds)
31 self._in_queue = Queue.Queue()
30 self._in_queue = Queue.Queue()
32
31
33 def call_handlers(self, msg):
32 def call_handlers(self, msg):
34 self._in_queue.put(msg)
33 self._in_queue.put(msg)
35
34
36 def get_msg(self, block=True, timeout=None):
35 def get_msg(self, block=True, timeout=None):
37 """ Gets a message if there is one that is ready. """
36 """ Gets a message if there is one that is ready. """
38 if timeout is None:
37 if timeout is None:
@@ -40,7 +39,7 b' class BlockingChannelMixin(object):'
40 # behavior, so wait for a week instead
39 # behavior, so wait for a week instead
41 timeout = 604800
40 timeout = 604800
42 return self._in_queue.get(block, timeout)
41 return self._in_queue.get(block, timeout)
43
42
44 def get_msgs(self):
43 def get_msgs(self):
45 """ Get all messages that are currently ready. """
44 """ Get all messages that are currently ready. """
46 msgs = []
45 msgs = []
@@ -50,7 +49,7 b' class BlockingChannelMixin(object):'
50 except Queue.Empty:
49 except Queue.Empty:
51 break
50 break
52 return msgs
51 return msgs
53
52
54 def msg_ready(self):
53 def msg_ready(self):
55 """ Is there a message that has been received? """
54 """ Is there a message that has been received? """
56 return not self._in_queue.empty()
55 return not self._in_queue.empty()
@@ -69,7 +68,7 b' class BlockingStdInChannel(BlockingChannelMixin, StdInChannel):'
69
68
70
69
71 class BlockingHBChannel(HBChannel):
70 class BlockingHBChannel(HBChannel):
72
71
73 # This kernel needs quicker monitoring, shorten to 1 sec.
72 # This kernel needs quicker monitoring, shorten to 1 sec.
74 # less than 0.5s is unreliable, and will get occasional
73 # less than 0.5s is unreliable, and will get occasional
75 # false reports of missed beats.
74 # false reports of missed beats.
@@ -78,13 +77,3 b' class BlockingHBChannel(HBChannel):'
78 def call_handlers(self, since_last_heartbeat):
77 def call_handlers(self, since_last_heartbeat):
79 """ Pause beating on missed heartbeat. """
78 """ Pause beating on missed heartbeat. """
80 pass
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.
1 """Base classes to manage a Client's interaction with a running kernel
2
3 TODO
4 * Create logger to handle debugging and console messages.
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 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
@@ -20,15 +17,9 b' from __future__ import absolute_import'
20 # Standard library imports
17 # Standard library imports
21 import atexit
18 import atexit
22 import errno
19 import errno
23 import json
24 from subprocess import Popen
25 import os
26 import signal
27 import sys
28 from threading import Thread
20 from threading import Thread
29 import time
21 import time
30
22
31 # System library imports
32 import zmq
23 import zmq
33 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
24 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
34 # during garbage collection of threads at exit:
25 # during garbage collection of threads at exit:
@@ -36,25 +27,11 b' from zmq import ZMQError'
36 from zmq.eventloop import ioloop, zmqstream
27 from zmq.eventloop import ioloop, zmqstream
37
28
38 # Local imports
29 # Local imports
39 from IPython.config.configurable import Configurable
30 from .channelsabc import (
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 (
52 ShellChannelABC, IOPubChannelABC,
31 ShellChannelABC, IOPubChannelABC,
53 HBChannelABC, StdInChannelABC,
32 HBChannelABC, StdInChannelABC,
54 KernelManagerABC
55 )
33 )
56
34
57
58 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
59 # Constants and exceptions
36 # Constants and exceptions
60 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
@@ -104,6 +81,7 b' class ZMQSocketChannel(Thread):'
104 stream = None
81 stream = None
105 _address = None
82 _address = None
106 _exiting = False
83 _exiting = False
84 proxy_methods = []
107
85
108 def __init__(self, context, session, address):
86 def __init__(self, context, session, address):
109 """Create a channel.
87 """Create a channel.
@@ -129,7 +107,7 b' class ZMQSocketChannel(Thread):'
129 address = "tcp://%s:%i" % address
107 address = "tcp://%s:%i" % address
130 self._address = address
108 self._address = address
131 atexit.register(self._notice_exit)
109 atexit.register(self._notice_exit)
132
110
133 def _notice_exit(self):
111 def _notice_exit(self):
134 self._exiting = True
112 self._exiting = True
135
113
@@ -170,11 +148,11 b' class ZMQSocketChannel(Thread):'
170
148
171 def _queue_send(self, msg):
149 def _queue_send(self, msg):
172 """Queue a message to be sent from the IOLoop's thread.
150 """Queue a message to be sent from the IOLoop's thread.
173
151
174 Parameters
152 Parameters
175 ----------
153 ----------
176 msg : message to send
154 msg : message to send
177
155
178 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
156 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
179 thread control of the action.
157 thread control of the action.
180 """
158 """
@@ -189,7 +167,7 b' class ZMQSocketChannel(Thread):'
189 """
167 """
190 ident,smsg = self.session.feed_identities(msg)
168 ident,smsg = self.session.feed_identities(msg)
191 self.call_handlers(self.session.unserialize(smsg))
169 self.call_handlers(self.session.unserialize(smsg))
192
170
193
171
194
172
195 class ShellChannel(ZMQSocketChannel):
173 class ShellChannel(ZMQSocketChannel):
@@ -198,6 +176,14 b' class ShellChannel(ZMQSocketChannel):'
198 command_queue = None
176 command_queue = None
199 # flag for whether execute requests should be allowed to call raw_input:
177 # flag for whether execute requests should be allowed to call raw_input:
200 allow_stdin = True
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 def __init__(self, context, session, address):
188 def __init__(self, context, session, address):
203 super(ShellChannel, self).__init__(context, session, address)
189 super(ShellChannel, self).__init__(context, session, address)
@@ -226,7 +212,7 b' class ShellChannel(ZMQSocketChannel):'
226
212
227 Subclasses should override this method to handle incoming messages.
213 Subclasses should override this method to handle incoming messages.
228 It is important to remember that this method is called in the thread
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 handlers are called in the application thread.
216 handlers are called in the application thread.
231 """
217 """
232 raise NotImplementedError('call_handlers must be defined in a subclass.')
218 raise NotImplementedError('call_handlers must be defined in a subclass.')
@@ -261,7 +247,7 b' class ShellChannel(ZMQSocketChannel):'
261 allow_stdin : bool, optional (default self.allow_stdin)
247 allow_stdin : bool, optional (default self.allow_stdin)
262 Flag for whether the kernel can send stdin requests to frontends.
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 If raw_input is called from code executed from such a frontend, a
251 If raw_input is called from code executed from such a frontend, a
266 StdinNotImplementedError will be raised.
252 StdinNotImplementedError will be raised.
267
253
@@ -275,8 +261,8 b' class ShellChannel(ZMQSocketChannel):'
275 user_expressions = {}
261 user_expressions = {}
276 if allow_stdin is None:
262 if allow_stdin is None:
277 allow_stdin = self.allow_stdin
263 allow_stdin = self.allow_stdin
278
264
279
265
280 # Don't waste network traffic if inputs are invalid
266 # Don't waste network traffic if inputs are invalid
281 if not isinstance(code, basestring):
267 if not isinstance(code, basestring):
282 raise ValueError('code %r must be a string' % code)
268 raise ValueError('code %r must be a string' % code)
@@ -474,6 +460,7 b' class StdInChannel(ZMQSocketChannel):'
474 """The stdin channel to handle raw_input requests that the kernel makes."""
460 """The stdin channel to handle raw_input requests that the kernel makes."""
475
461
476 msg_queue = None
462 msg_queue = None
463 proxy_methods = ['input']
477
464
478 def __init__(self, context, session, address):
465 def __init__(self, context, session, address):
479 super(StdInChannel, self).__init__(context, session, address)
466 super(StdInChannel, self).__init__(context, session, address)
@@ -543,17 +530,17 b' class HBChannel(ZMQSocketChannel):'
543 self.socket = self.context.socket(zmq.REQ)
530 self.socket = self.context.socket(zmq.REQ)
544 self.socket.setsockopt(zmq.LINGER, 0)
531 self.socket.setsockopt(zmq.LINGER, 0)
545 self.socket.connect(self.address)
532 self.socket.connect(self.address)
546
533
547 self.poller.register(self.socket, zmq.POLLIN)
534 self.poller.register(self.socket, zmq.POLLIN)
548
535
549 def _poll(self, start_time):
536 def _poll(self, start_time):
550 """poll for heartbeat replies until we reach self.time_to_dead.
537 """poll for heartbeat replies until we reach self.time_to_dead.
551
538
552 Ignores interrupts, and returns the result of poll(), which
539 Ignores interrupts, and returns the result of poll(), which
553 will be an empty list if no messages arrived before the timeout,
540 will be an empty list if no messages arrived before the timeout,
554 or the event tuple if there is a message to receive.
541 or the event tuple if there is a message to receive.
555 """
542 """
556
543
557 until_dead = self.time_to_dead - (time.time() - start_time)
544 until_dead = self.time_to_dead - (time.time() - start_time)
558 # ensure poll at least once
545 # ensure poll at least once
559 until_dead = max(until_dead, 1e-3)
546 until_dead = max(until_dead, 1e-3)
@@ -584,13 +571,13 b' class HBChannel(ZMQSocketChannel):'
584 self._create_socket()
571 self._create_socket()
585 self._running = True
572 self._running = True
586 self._beating = True
573 self._beating = True
587
574
588 while self._running:
575 while self._running:
589 if self._pause:
576 if self._pause:
590 # just sleep, and skip the rest of the loop
577 # just sleep, and skip the rest of the loop
591 time.sleep(self.time_to_dead)
578 time.sleep(self.time_to_dead)
592 continue
579 continue
593
580
594 since_last_heartbeat = 0.0
581 since_last_heartbeat = 0.0
595 # io.rprint('Ping from HB channel') # dbg
582 # io.rprint('Ping from HB channel') # dbg
596 # no need to catch EFSM here, because the previous event was
583 # no need to catch EFSM here, because the previous event was
@@ -651,474 +638,7 b' class HBChannel(ZMQSocketChannel):'
651 raise NotImplementedError('call_handlers must be defined in a subclass.')
638 raise NotImplementedError('call_handlers must be defined in a subclass.')
652
639
653
640
654 #-----------------------------------------------------------------------------
641 #---------------------------------------------------------------------#-----------------------------------------------------------------------------
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 #-----------------------------------------------------------------------------
1122 # ABC Registration
642 # ABC Registration
1123 #-----------------------------------------------------------------------------
643 #-----------------------------------------------------------------------------
1124
644
@@ -1126,5 +646,3 b' ShellChannelABC.register(ShellChannel)'
1126 IOPubChannelABC.register(IOPubChannel)
646 IOPubChannelABC.register(IOPubChannel)
1127 HBChannelABC.register(HBChannel)
647 HBChannelABC.register(HBChannel)
1128 StdInChannelABC.register(StdInChannel)
648 StdInChannelABC.register(StdInChannel)
1129 KernelManagerABC.register(KernelManager)
1130
@@ -17,6 +17,8 b' Authors:'
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from __future__ import absolute_import
21
20 import glob
22 import glob
21 import json
23 import json
22 import os
24 import os
@@ -26,14 +28,21 b' from getpass import getpass'
26 from subprocess import Popen, PIPE
28 from subprocess import Popen, PIPE
27 import tempfile
29 import tempfile
28
30
31 import zmq
32
29 # external imports
33 # external imports
30 from IPython.external.ssh import tunnel
34 from IPython.external.ssh import tunnel
31
35
32 # IPython imports
36 # IPython imports
37 # from IPython.config import Configurable
33 from IPython.core.profiledir import ProfileDir
38 from IPython.core.profiledir import ProfileDir
34 from IPython.utils.localinterfaces import LOCALHOST
39 from IPython.utils.localinterfaces import LOCALHOST
35 from IPython.utils.path import filefind, get_ipython_dir
40 from IPython.utils.path import filefind, get_ipython_dir
36 from IPython.utils.py3compat import str_to_bytes, bytes_to_str
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 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
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 """Generates a JSON config file, including the selection of random ports.
54 """Generates a JSON config file, including the selection of random ports.
46
55
47 Parameters
56 Parameters
@@ -51,16 +60,19 b' def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, '
51 The path to the file to write
60 The path to the file to write
52
61
53 shell_port : int, optional
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 iopub_port : int, optional
65 iopub_port : int, optional
57 The port to use for the SUB channel.
66 The port to use for the SUB channel.
58
67
59 stdin_port : int, optional
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 hb_port : int, optional
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 ip : str, optional
77 ip : str, optional
66 The ip address the kernel will bind to.
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 # Find open ports as necessary.
88 # Find open ports as necessary.
77
89
78 ports = []
90 ports = []
79 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
91 ports_needed = int(shell_port <= 0) + \
80 int(stdin_port <= 0) + int(hb_port <= 0)
92 int(iopub_port <= 0) + \
93 int(stdin_port <= 0) + \
94 int(control_port <= 0) + \
95 int(hb_port <= 0)
81 if transport == 'tcp':
96 if transport == 'tcp':
82 for i in range(ports_needed):
97 for i in range(ports_needed):
83 sock = socket.socket()
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 iopub_port = ports.pop(0)
115 iopub_port = ports.pop(0)
101 if stdin_port <= 0:
116 if stdin_port <= 0:
102 stdin_port = ports.pop(0)
117 stdin_port = ports.pop(0)
118 if control_port <= 0:
119 control_port = ports.pop(0)
103 if hb_port <= 0:
120 if hb_port <= 0:
104 hb_port = ports.pop(0)
121 hb_port = ports.pop(0)
105
122
106 cfg = dict( shell_port=shell_port,
123 cfg = dict( shell_port=shell_port,
107 iopub_port=iopub_port,
124 iopub_port=iopub_port,
108 stdin_port=stdin_port,
125 stdin_port=stdin_port,
126 control_port=control_port,
109 hb_port=hb_port,
127 hb_port=hb_port,
110 )
128 )
111 cfg['ip'] = ip
129 cfg['ip'] = ip
@@ -286,7 +304,9 b' def connect_qtconsole(connection_file=None, argv=None, profile=None):'
286 "qtconsoleapp.main()"
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 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
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 return tuple(lports)
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 __all__ = [
533 __all__ = [
341 'write_connection_file',
534 'write_connection_file',
342 'get_connection_file',
535 'get_connection_file',
@@ -344,4 +537,4 b' __all__ = ['
344 'get_connection_info',
537 'get_connection_info',
345 'connect_qtconsole',
538 'connect_qtconsole',
346 'tunnel_to_kernel',
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 Useful for test suites and blocking terminal interfaces.
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 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from __future__ import print_function
16
15
17 # Local imports.
16 # IPython imports
18 from IPython.utils.io import raw_print
17 from IPython.utils.io import raw_print
19 from IPython.utils.traitlets import Type
18 from IPython.utils.traitlets import Type
20 from kernelmanager import InProcessKernelManager, InProcessShellChannel, \
19 from IPython.kernel.blocking.channels import BlockingChannelMixin
21 InProcessIOPubChannel, InProcessStdInChannel
22 from IPython.kernel.blockingkernelmanager 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 # Blocking kernel manager
30 # Blocking kernel manager
@@ -33,7 +37,7 b' class BlockingInProcessIOPubChannel(BlockingChannelMixin, InProcessIOPubChannel)'
33 pass
37 pass
34
38
35 class BlockingInProcessStdInChannel(BlockingChannelMixin, InProcessStdInChannel):
39 class BlockingInProcessStdInChannel(BlockingChannelMixin, InProcessStdInChannel):
36
40
37 def call_handlers(self, msg):
41 def call_handlers(self, msg):
38 """ Overridden for the in-process channel.
42 """ Overridden for the in-process channel.
39
43
@@ -41,12 +45,12 b' class BlockingInProcessStdInChannel(BlockingChannelMixin, InProcessStdInChannel)'
41 """
45 """
42 msg_type = msg['header']['msg_type']
46 msg_type = msg['header']['msg_type']
43 if msg_type == 'input_request':
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 prompt = msg['content']['prompt']
49 prompt = msg['content']['prompt']
46 raw_print(prompt, end='')
50 raw_print(prompt, end='')
47 self.input(_raw_input())
51 self.input(_raw_input())
48
52
49 class BlockingInProcessKernelManager(InProcessKernelManager):
53 class BlockingInProcessKernelClient(InProcessKernelClient):
50
54
51 # The classes to use for the various channels.
55 # The classes to use for the various channels.
52 shell_channel_class = Type(BlockingInProcessShellChannel)
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 # Copyright (C) 2012 The IPython Development Team
4 # Copyright (C) 2012 The IPython Development Team
@@ -11,15 +11,13 b''
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 # Local imports.
14 # IPython imports
15 from IPython.config.configurable import Configurable
15 from IPython.kernel.channelsabc import (
16 from IPython.utils.traitlets import Any, Instance, Type
17 from IPython.kernel.kernelmanagerabc import (
18 ShellChannelABC, IOPubChannelABC,
16 ShellChannelABC, IOPubChannelABC,
19 HBChannelABC, StdInChannelABC,
17 HBChannelABC, StdInChannelABC,
20 KernelManagerABC
21 )
18 )
22
19
20 # Local imports
23 from .socket import DummySocket
21 from .socket import DummySocket
24
22
25 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
@@ -28,10 +26,11 b' from .socket import DummySocket'
28
26
29 class InProcessChannel(object):
27 class InProcessChannel(object):
30 """Base class for in-process channels."""
28 """Base class for in-process channels."""
29 proxy_methods = []
31
30
32 def __init__(self, manager):
31 def __init__(self, client):
33 super(InProcessChannel, self).__init__()
32 super(InProcessChannel, self).__init__()
34 self.manager = manager
33 self.client = client
35 self._is_alive = False
34 self._is_alive = False
36
35
37 #--------------------------------------------------------------------------
36 #--------------------------------------------------------------------------
@@ -77,10 +76,17 b' class InProcessChannel(object):'
77
76
78
77
79 class InProcessShellChannel(InProcessChannel):
78 class InProcessShellChannel(InProcessChannel):
80 """See `IPython.kernel.kernelmanager.ShellChannel` for docstrings."""
79 """See `IPython.kernel.channels.ShellChannel` for docstrings."""
81
80
82 # flag for whether execute requests should be allowed to call raw_input
81 # flag for whether execute requests should be allowed to call raw_input
83 allow_stdin = True
82 allow_stdin = True
83 proxy_methods = [
84 'execute',
85 'complete',
86 'object_info',
87 'history',
88 'shutdown',
89 ]
84
90
85 #--------------------------------------------------------------------------
91 #--------------------------------------------------------------------------
86 # ShellChannel interface
92 # ShellChannel interface
@@ -94,26 +100,26 b' class InProcessShellChannel(InProcessChannel):'
94 user_variables=user_variables,
100 user_variables=user_variables,
95 user_expressions=user_expressions,
101 user_expressions=user_expressions,
96 allow_stdin=allow_stdin)
102 allow_stdin=allow_stdin)
97 msg = self.manager.session.msg('execute_request', content)
103 msg = self.client.session.msg('execute_request', content)
98 self._dispatch_to_kernel(msg)
104 self._dispatch_to_kernel(msg)
99 return msg['header']['msg_id']
105 return msg['header']['msg_id']
100
106
101 def complete(self, text, line, cursor_pos, block=None):
107 def complete(self, text, line, cursor_pos, block=None):
102 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
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 self._dispatch_to_kernel(msg)
110 self._dispatch_to_kernel(msg)
105 return msg['header']['msg_id']
111 return msg['header']['msg_id']
106
112
107 def object_info(self, oname, detail_level=0):
113 def object_info(self, oname, detail_level=0):
108 content = dict(oname=oname, detail_level=detail_level)
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 self._dispatch_to_kernel(msg)
116 self._dispatch_to_kernel(msg)
111 return msg['header']['msg_id']
117 return msg['header']['msg_id']
112
118
113 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
119 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
114 content = dict(raw=raw, output=output,
120 content = dict(raw=raw, output=output,
115 hist_access_type=hist_access_type, **kwds)
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 self._dispatch_to_kernel(msg)
123 self._dispatch_to_kernel(msg)
118 return msg['header']['msg_id']
124 return msg['header']['msg_id']
119
125
@@ -128,38 +134,40 b' class InProcessShellChannel(InProcessChannel):'
128 def _dispatch_to_kernel(self, msg):
134 def _dispatch_to_kernel(self, msg):
129 """ Send a message to the kernel and handle a reply.
135 """ Send a message to the kernel and handle a reply.
130 """
136 """
131 kernel = self.manager.kernel
137 kernel = self.client.kernel
132 if kernel is None:
138 if kernel is None:
133 raise RuntimeError('Cannot send request. No kernel exists.')
139 raise RuntimeError('Cannot send request. No kernel exists.')
134
140
135 stream = DummySocket()
141 stream = DummySocket()
136 self.manager.session.send(stream, msg)
142 self.client.session.send(stream, msg)
137 msg_parts = stream.recv_multipart()
143 msg_parts = stream.recv_multipart()
138 kernel.dispatch_shell(stream, msg_parts)
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 self.call_handlers_later(reply_msg)
147 self.call_handlers_later(reply_msg)
142
148
143
149
144 class InProcessIOPubChannel(InProcessChannel):
150 class InProcessIOPubChannel(InProcessChannel):
145 """See `IPython.kernel.kernelmanager.IOPubChannel` for docstrings."""
151 """See `IPython.kernel.channels.IOPubChannel` for docstrings."""
146
152
147 def flush(self, timeout=1.0):
153 def flush(self, timeout=1.0):
148 pass
154 pass
149
155
150
156
151 class InProcessStdInChannel(InProcessChannel):
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 def input(self, string):
162 def input(self, string):
155 kernel = self.manager.kernel
163 kernel = self.client.kernel
156 if kernel is None:
164 if kernel is None:
157 raise RuntimeError('Cannot send input reply. No kernel exists.')
165 raise RuntimeError('Cannot send input reply. No kernel exists.')
158 kernel.raw_input_str = string
166 kernel.raw_input_str = string
159
167
160
168
161 class InProcessHBChannel(InProcessChannel):
169 class InProcessHBChannel(InProcessChannel):
162 """See `IPython.kernel.kernelmanager.HBChannel` for docstrings."""
170 """See `IPython.kernel.channels.HBChannel` for docstrings."""
163
171
164 time_to_dead = 3.0
172 time_to_dead = 3.0
165
173
@@ -176,133 +184,6 b' class InProcessHBChannel(InProcessChannel):'
176 def is_beating(self):
184 def is_beating(self):
177 return not self._pause
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 # ABC Registration
188 # ABC Registration
308 #-----------------------------------------------------------------------------
189 #-----------------------------------------------------------------------------
@@ -311,4 +192,3 b' ShellChannelABC.register(InProcessShellChannel)'
311 IOPubChannelABC.register(InProcessIOPubChannel)
192 IOPubChannelABC.register(InProcessIOPubChannel)
312 HBChannelABC.register(InProcessHBChannel)
193 HBChannelABC.register(InProcessHBChannel)
313 StdInChannelABC.register(InProcessStdInChannel)
194 StdInChannelABC.register(InProcessStdInChannel)
314 KernelManagerABC.register(InProcessKernelManager)
@@ -37,7 +37,8 b' class InProcessKernel(Kernel):'
37
37
38 # The frontends connected to this kernel.
38 # The frontends connected to this kernel.
39 frontends = List(
39 frontends = List(
40 Instance('IPython.kernel.inprocess.kernelmanager.InProcessKernelManager'))
40 Instance('IPython.kernel.inprocess.client.InProcessKernelClient')
41 )
41
42
42 # The GUI environment that the kernel is running under. This need not be
43 # The GUI environment that the kernel is running under. This need not be
43 # specified for the normal operation for the kernel, but is required for
44 # specified for the normal operation for the kernel, but is required for
@@ -16,8 +16,8 b' import sys'
16 import unittest
16 import unittest
17
17
18 # Local imports
18 # Local imports
19 from IPython.kernel.inprocess.blockingkernelmanager import \
19 from IPython.kernel.inprocess.blocking import BlockingInProcessKernelClient
20 BlockingInProcessKernelManager
20 from IPython.kernel.inprocess.manager import InProcessKernelManager
21 from IPython.kernel.inprocess.ipkernel import InProcessKernel
21 from IPython.kernel.inprocess.ipkernel import InProcessKernel
22 from IPython.testing.decorators import skipif_not_matplotlib
22 from IPython.testing.decorators import skipif_not_matplotlib
23 from IPython.utils.io import capture_output
23 from IPython.utils.io import capture_output
@@ -29,33 +29,35 b' from IPython.utils import py3compat'
29
29
30 class InProcessKernelTestCase(unittest.TestCase):
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 @skipif_not_matplotlib
38 @skipif_not_matplotlib
33 def test_pylab(self):
39 def test_pylab(self):
34 """ Does pylab work in the in-process kernel?
40 """ Does pylab work in the in-process kernel?
35 """
41 """
36 km = BlockingInProcessKernelManager()
42 kc = self.kc
37 km.start_kernel()
43 kc.execute('%pylab')
38 km.shell_channel.execute('%pylab')
44 msg = get_stream_message(kc)
39 msg = get_stream_message(km)
40 self.assert_('Welcome to pylab' in msg['content']['data'])
45 self.assert_('Welcome to pylab' in msg['content']['data'])
41
46
42 def test_raw_input(self):
47 def test_raw_input(self):
43 """ Does the in-process kernel handle raw_input correctly?
48 """ Does the in-process kernel handle raw_input correctly?
44 """
49 """
45 km = BlockingInProcessKernelManager()
46 km.start_kernel()
47
48 io = StringIO('foobar\n')
50 io = StringIO('foobar\n')
49 sys_stdin = sys.stdin
51 sys_stdin = sys.stdin
50 sys.stdin = io
52 sys.stdin = io
51 try:
53 try:
52 if py3compat.PY3:
54 if py3compat.PY3:
53 km.shell_channel.execute('x = input()')
55 self.kc.execute('x = input()')
54 else:
56 else:
55 km.shell_channel.execute('x = raw_input()')
57 self.kc.execute('x = raw_input()')
56 finally:
58 finally:
57 sys.stdin = sys_stdin
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 def test_stdout(self):
62 def test_stdout(self):
61 """ Does the in-process kernel correctly capture IO?
63 """ Does the in-process kernel correctly capture IO?
@@ -66,21 +68,21 b' class InProcessKernelTestCase(unittest.TestCase):'
66 kernel.shell.run_cell('print("foo")')
68 kernel.shell.run_cell('print("foo")')
67 self.assertEqual(io.stdout, 'foo\n')
69 self.assertEqual(io.stdout, 'foo\n')
68
70
69 km = BlockingInProcessKernelManager(kernel=kernel)
71 kc = BlockingInProcessKernelClient(kernel=kernel)
70 kernel.frontends.append(km)
72 kernel.frontends.append(kc)
71 km.shell_channel.execute('print("bar")')
73 kc.shell_channel.execute('print("bar")')
72 msg = get_stream_message(km)
74 msg = get_stream_message(kc)
73 self.assertEqual(msg['content']['data'], 'bar\n')
75 self.assertEqual(msg['content']['data'], 'bar\n')
74
76
75 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
76 # Utility functions
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 """ Gets a single stream message synchronously from the sub channel.
82 """ Gets a single stream message synchronously from the sub channel.
81 """
83 """
82 while True:
84 while True:
83 msg = kernel_manager.iopub_channel.get_msg(timeout=timeout)
85 msg = kernel_client.get_iopub_msg(timeout=timeout)
84 if msg['header']['msg_type'] == 'stream':
86 if msg['header']['msg_type'] == 'stream':
85 return msg
87 return msg
86
88
@@ -14,9 +14,9 b' from __future__ import print_function'
14 import unittest
14 import unittest
15
15
16 # Local imports
16 # Local imports
17 from IPython.kernel.inprocess.blockingkernelmanager import \
17 from IPython.kernel.inprocess.blocking import BlockingInProcessKernelClient
18 BlockingInProcessKernelManager
19 from IPython.kernel.inprocess.ipkernel import InProcessKernel
18 from IPython.kernel.inprocess.ipkernel import InProcessKernel
19 from IPython.kernel.inprocess.manager import InProcessKernelManager
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Test case
22 # Test case
@@ -24,20 +24,22 b' from IPython.kernel.inprocess.ipkernel import InProcessKernel'
24
24
25 class InProcessKernelManagerTestCase(unittest.TestCase):
25 class InProcessKernelManagerTestCase(unittest.TestCase):
26
26
27 def test_inteface(self):
27 def test_interface(self):
28 """ Does the in-process kernel manager implement the basic KM interface?
28 """ Does the in-process kernel manager implement the basic KM interface?
29 """
29 """
30 km = BlockingInProcessKernelManager()
30 km = InProcessKernelManager()
31 self.assert_(not km.channels_running)
32 self.assert_(not km.has_kernel)
31 self.assert_(not km.has_kernel)
33
32
34 km.start_channels()
35 self.assert_(km.channels_running)
36
37 km.start_kernel()
33 km.start_kernel()
38 self.assert_(km.has_kernel)
34 self.assert_(km.has_kernel)
39 self.assert_(km.kernel is not None)
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 old_kernel = km.kernel
43 old_kernel = km.kernel
42 km.restart_kernel()
44 km.restart_kernel()
43 self.assert_(km.kernel is not None)
45 self.assert_(km.kernel is not None)
@@ -49,37 +51,43 b' class InProcessKernelManagerTestCase(unittest.TestCase):'
49 self.assertRaises(NotImplementedError, km.interrupt_kernel)
51 self.assertRaises(NotImplementedError, km.interrupt_kernel)
50 self.assertRaises(NotImplementedError, km.signal_kernel, 9)
52 self.assertRaises(NotImplementedError, km.signal_kernel, 9)
51
53
52 km.stop_channels()
54 kc.stop_channels()
53 self.assert_(not km.channels_running)
55 self.assert_(not kc.channels_running)
54
56
55 def test_execute(self):
57 def test_execute(self):
56 """ Does executing code in an in-process kernel work?
58 """ Does executing code in an in-process kernel work?
57 """
59 """
58 km = BlockingInProcessKernelManager()
60 km = InProcessKernelManager()
59 km.start_kernel()
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 self.assertEquals(km.kernel.shell.user_ns['foo'], 1)
65 self.assertEquals(km.kernel.shell.user_ns['foo'], 1)
62
66
63 def test_complete(self):
67 def test_complete(self):
64 """ Does requesting completion from an in-process kernel work?
68 """ Does requesting completion from an in-process kernel work?
65 """
69 """
66 km = BlockingInProcessKernelManager()
70 km = InProcessKernelManager()
67 km.start_kernel()
71 km.start_kernel()
72 kc = BlockingInProcessKernelClient(kernel=km.kernel)
73 kc.start_channels()
68 km.kernel.shell.push({'my_bar': 0, 'my_baz': 1})
74 km.kernel.shell.push({'my_bar': 0, 'my_baz': 1})
69 km.shell_channel.complete('my_ba', 'my_ba', 5)
75 kc.complete('my_ba', 'my_ba', 5)
70 msg = km.shell_channel.get_msg()
76 msg = kc.get_shell_msg()
71 self.assertEquals(msg['header']['msg_type'], 'complete_reply')
77 self.assertEqual(msg['header']['msg_type'], 'complete_reply')
72 self.assertEquals(sorted(msg['content']['matches']),
78 self.assertEqual(sorted(msg['content']['matches']),
73 ['my_bar', 'my_baz'])
79 ['my_bar', 'my_baz'])
74
80
75 def test_object_info(self):
81 def test_object_info(self):
76 """ Does requesting object information from an in-process kernel work?
82 """ Does requesting object information from an in-process kernel work?
77 """
83 """
78 km = BlockingInProcessKernelManager()
84 km = InProcessKernelManager()
79 km.start_kernel()
85 km.start_kernel()
86 kc = BlockingInProcessKernelClient(kernel=km.kernel)
87 kc.start_channels()
80 km.kernel.shell.user_ns['foo'] = 1
88 km.kernel.shell.user_ns['foo'] = 1
81 km.shell_channel.object_info('foo')
89 kc.object_info('foo')
82 msg = km.shell_channel.get_msg()
90 msg = kc.get_shell_msg()
83 self.assertEquals(msg['header']['msg_type'], 'object_info_reply')
91 self.assertEquals(msg['header']['msg_type'], 'object_info_reply')
84 self.assertEquals(msg['content']['name'], 'foo')
92 self.assertEquals(msg['content']['name'], 'foo')
85 self.assertEquals(msg['content']['type_name'], 'int')
93 self.assertEquals(msg['content']['type_name'], 'int')
@@ -87,11 +95,13 b' class InProcessKernelManagerTestCase(unittest.TestCase):'
87 def test_history(self):
95 def test_history(self):
88 """ Does requesting history from an in-process kernel work?
96 """ Does requesting history from an in-process kernel work?
89 """
97 """
90 km = BlockingInProcessKernelManager()
98 km = InProcessKernelManager()
91 km.start_kernel()
99 km.start_kernel()
92 km.shell_channel.execute('%who')
100 kc = BlockingInProcessKernelClient(kernel=km.kernel)
93 km.shell_channel.history(hist_access_type='tail', n=1)
101 kc.start_channels()
94 msg = km.shell_channel.get_msgs()[-1]
102 kc.execute('%who')
103 kc.history(hist_access_type='tail', n=1)
104 msg = kc.shell_channel.get_msgs()[-1]
95 self.assertEquals(msg['header']['msg_type'], 'history_reply')
105 self.assertEquals(msg['header']['msg_type'], 'history_reply')
96 history = msg['content']['history']
106 history = msg['content']['history']
97 self.assertEquals(len(history), 1)
107 self.assertEquals(len(history), 1)
@@ -220,7 +220,6 b' class KernelManagerABC(object):'
220 def signal_kernel(self, signum):
220 def signal_kernel(self, signum):
221 pass
221 pass
222
222
223 @abc.abstractproperty
223 @abc.abstractmethod
224 def is_alive(self):
224 def is_alive(self):
225 pass
225 pass
226
@@ -22,13 +22,13 b' import os'
22 import uuid
22 import uuid
23
23
24 import zmq
24 import zmq
25 from zmq.eventloop.zmqstream import ZMQStream
26
25
27 from IPython.config.configurable import LoggingConfigurable
26 from IPython.config.configurable import LoggingConfigurable
28 from IPython.utils.importstring import import_item
27 from IPython.utils.importstring import import_item
29 from IPython.utils.traitlets import (
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 # Classes
33 # Classes
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
@@ -37,11 +37,28 b' class DuplicateKernelError(Exception):'
37 pass
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 class MultiKernelManager(LoggingConfigurable):
57 class MultiKernelManager(LoggingConfigurable):
41 """A class for managing multiple kernels."""
58 """A class for managing multiple kernels."""
42
59
43 kernel_manager_class = DottedObjectName(
60 kernel_manager_class = DottedObjectName(
44 "IPython.kernel.blockingkernelmanager.BlockingKernelManager", config=True,
61 "IPython.kernel.ioloop.IOLoopKernelManager", config=True,
45 help="""The kernel manager class. This is configurable to allow
62 help="""The kernel manager class. This is configurable to allow
46 subclassing of the KernelManager for customized behavior.
63 subclassing of the KernelManager for customized behavior.
47 """
64 """
@@ -56,7 +73,7 b' class MultiKernelManager(LoggingConfigurable):'
56 context = Instance('zmq.Context')
73 context = Instance('zmq.Context')
57 def _context_default(self):
74 def _context_default(self):
58 return zmq.Context.instance()
75 return zmq.Context.instance()
59
76
60 connection_dir = Unicode('')
77 connection_dir = Unicode('')
61
78
62 _kernels = Dict()
79 _kernels = Dict()
@@ -93,14 +110,13 b' class MultiKernelManager(LoggingConfigurable):'
93 # including things like its transport and ip.
110 # including things like its transport and ip.
94 km = self.kernel_manager_factory(connection_file=os.path.join(
111 km = self.kernel_manager_factory(connection_file=os.path.join(
95 self.connection_dir, "kernel-%s.json" % kernel_id),
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 km.start_kernel(**kwargs)
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 self._kernels[kernel_id] = km
116 self._kernels[kernel_id] = km
102 return kernel_id
117 return kernel_id
103
118
119 @kernel_method
104 def shutdown_kernel(self, kernel_id, now=False):
120 def shutdown_kernel(self, kernel_id, now=False):
105 """Shutdown a kernel by its kernel uuid.
121 """Shutdown a kernel by its kernel uuid.
106
122
@@ -111,16 +127,25 b' class MultiKernelManager(LoggingConfigurable):'
111 now : bool
127 now : bool
112 Should the kernel be shutdown forcibly using a signal.
128 Should the kernel be shutdown forcibly using a signal.
113 """
129 """
114 k = self.get_kernel(kernel_id)
130 self.log.info("Kernel shutdown: %s" % kernel_id)
115 k.shutdown_kernel(now=now)
131 self.remove_kernel(kernel_id)
116 k.shell_channel.stop()
132
117 del self._kernels[kernel_id]
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 def shutdown_all(self, now=False):
143 def shutdown_all(self, now=False):
120 """Shutdown all kernels."""
144 """Shutdown all kernels."""
121 for kid in self.list_kernel_ids():
145 for kid in self.list_kernel_ids():
122 self.shutdown_kernel(kid, now=now)
146 self.shutdown_kernel(kid, now=now)
123
147
148 @kernel_method
124 def interrupt_kernel(self, kernel_id):
149 def interrupt_kernel(self, kernel_id):
125 """Interrupt (SIGINT) the kernel by its uuid.
150 """Interrupt (SIGINT) the kernel by its uuid.
126
151
@@ -129,8 +154,9 b' class MultiKernelManager(LoggingConfigurable):'
129 kernel_id : uuid
154 kernel_id : uuid
130 The id of the kernel to interrupt.
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 def signal_kernel(self, kernel_id, signum):
160 def signal_kernel(self, kernel_id, signum):
135 """Sends a signal to the kernel by its uuid.
161 """Sends a signal to the kernel by its uuid.
136
162
@@ -142,8 +168,9 b' class MultiKernelManager(LoggingConfigurable):'
142 kernel_id : uuid
168 kernel_id : uuid
143 The id of the kernel to signal.
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 def restart_kernel(self, kernel_id):
174 def restart_kernel(self, kernel_id):
148 """Restart a kernel by its uuid, keeping the same ports.
175 """Restart a kernel by its uuid, keeping the same ports.
149
176
@@ -152,7 +179,25 b' class MultiKernelManager(LoggingConfigurable):'
152 kernel_id : uuid
179 kernel_id : uuid
153 The id of the kernel to interrupt.
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 def get_kernel(self, kernel_id):
202 def get_kernel(self, kernel_id):
158 """Get the single KernelManager object for a kernel by its uuid.
203 """Get the single KernelManager object for a kernel by its uuid.
@@ -162,12 +207,18 b' class MultiKernelManager(LoggingConfigurable):'
162 kernel_id : uuid
207 kernel_id : uuid
163 The id of the kernel.
208 The id of the kernel.
164 """
209 """
165 km = self._kernels.get(kernel_id)
210 self._check_kernel_id(kernel_id)
166 if km is not None:
211 return self._kernels[kernel_id]
167 return km
212
168 else:
213 @kernel_method
169 raise KeyError("Kernel with id not found: %s" % kernel_id)
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 def get_connection_info(self, kernel_id):
222 def get_connection_info(self, kernel_id):
172 """Return a dictionary of connection data for a kernel.
223 """Return a dictionary of connection data for a kernel.
173
224
@@ -184,76 +235,67 b' class MultiKernelManager(LoggingConfigurable):'
184 numbers of the different channels (stdin_port, iopub_port,
235 numbers of the different channels (stdin_port, iopub_port,
185 shell_port, hb_port).
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):
239 @kernel_method
215 """Return a ZMQStream object connected to the iopub channel.
240 def connect_iopub(self, kernel_id, identity=None):
241 """Return a zmq Socket connected to the iopub channel.
216
242
217 Parameters
243 Parameters
218 ==========
244 ==========
219 kernel_id : uuid
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 Returns
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):
255 @kernel_method
231 """Return a ZMQStream object connected to the shell channel.
256 def connect_shell(self, kernel_id, identity=None):
257 """Return a zmq Socket connected to the shell channel.
232
258
233 Parameters
259 Parameters
234 ==========
260 ==========
235 kernel_id : uuid
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 Returns
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):
271 @kernel_method
246 """Return a ZMQStream object connected to the hb channel.
272 def connect_stdin(self, kernel_id, identity=None):
273 """Return a zmq Socket connected to the stdin channel.
247
274
248 Parameters
275 Parameters
249 ==========
276 ==========
250 kernel_id : uuid
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 Returns
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 from IPython.testing import decorators as dec
7 from IPython.testing import decorators as dec
8
8
9 from IPython.config.loader import Config
9 from IPython.config.loader import Config
10 from IPython.kernel.kernelmanager import KernelManager
10 from IPython.kernel import KernelManager
11
11
12 class TestKernelManager(TestCase):
12 class TestKernelManager(TestCase):
13
13
14 def _get_tcp_km(self):
14 def _get_tcp_km(self):
15 return KernelManager()
15 c = Config()
16 km = KernelManager(config=c)
17 return km
16
18
17 def _get_ipc_km(self):
19 def _get_ipc_km(self):
18 c = Config()
20 c = Config()
@@ -23,8 +25,9 b' class TestKernelManager(TestCase):'
23
25
24 def _run_lifecycle(self, km):
26 def _run_lifecycle(self, km):
25 km.start_kernel(stdout=PIPE, stderr=PIPE)
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 km.restart_kernel()
29 km.restart_kernel()
30 self.assertTrue(km.is_alive())
28 # We need a delay here to give the restarting kernel a chance to
31 # We need a delay here to give the restarting kernel a chance to
29 # restart. Otherwise, the interrupt will kill it, causing the test
32 # restart. Otherwise, the interrupt will kill it, causing the test
30 # suite to hang. The reason it *hangs* is that the shutdown
33 # suite to hang. The reason it *hangs* is that the shutdown
@@ -35,7 +38,6 b' class TestKernelManager(TestCase):'
35 km.interrupt_kernel()
38 km.interrupt_kernel()
36 self.assertTrue(isinstance(km, KernelManager))
39 self.assertTrue(isinstance(km, KernelManager))
37 km.shutdown_kernel()
40 km.shutdown_kernel()
38 km.shell_channel.stop()
39
41
40 def test_tcp_lifecycle(self):
42 def test_tcp_lifecycle(self):
41 km = self._get_tcp_km()
43 km = self._get_tcp_km()
@@ -15,7 +15,7 b' from Queue import Empty'
15
15
16 import nose.tools as nt
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 from IPython.testing import decorators as dec
21 from IPython.testing import decorators as dec
@@ -29,28 +29,29 b' from IPython.utils.traitlets import ('
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 def setup():
31 def setup():
32 global KM
32 global KM, KC
33 KM = BlockingKernelManager()
33 KM = KernelManager()
34
34 KM.client_factory = BlockingKernelClient
35 KM.start_kernel(stdout=PIPE, stderr=PIPE)
35 KM.start_kernel(stdout=PIPE, stderr=PIPE)
36 KM.start_channels()
36 KC = KM.client()
37 KC.start_channels()
37
38
38 # wait for kernel to be ready
39 # wait for kernel to be ready
39 KM.shell_channel.execute("pass")
40 KC.execute("pass")
40 KM.shell_channel.get_msg(block=True, timeout=5)
41 KC.get_shell_msg(block=True, timeout=5)
41 flush_channels()
42 flush_channels()
42
43
43
44
44 def teardown():
45 def teardown():
45 KM.stop_channels()
46 KC.stop_channels()
46 KM.shutdown_kernel()
47 KM.shutdown_kernel()
47
48
48
49
49 def flush_channels(km=None):
50 def flush_channels(kc=None):
50 if km is None:
51 km = KM
52 """flush any messages waiting on the queue"""
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 while True:
55 while True:
55 try:
56 try:
56 msg = channel.get_msg(block=True, timeout=0.1)
57 msg = channel.get_msg(block=True, timeout=0.1)
@@ -60,22 +61,17 b' def flush_channels(km=None):'
60 list(validate_message(msg))
61 list(validate_message(msg))
61
62
62
63
63 def execute(code='', km=None, **kwargs):
64 def execute(code='', kc=None, **kwargs):
64 """wrapper for doing common steps for validating an execution request"""
65 """wrapper for doing common steps for validating an execution request"""
65 if km is None:
66 msg_id = KC.execute(code=code, **kwargs)
66 km = KM
67 reply = KC.get_shell_msg(timeout=2)
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)
72 list(validate_message(reply, 'execute_reply', msg_id))
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 list(validate_message(busy, 'status', msg_id))
70 list(validate_message(busy, 'status', msg_id))
75 nt.assert_equal(busy['content']['execution_state'], 'busy')
71 nt.assert_equal(busy['content']['execution_state'], 'busy')
76
72
77 if not kwargs.get('silent'):
73 if not kwargs.get('silent'):
78 pyin = sub.get_msg(timeout=2)
74 pyin = KC.get_iopub_msg(timeout=2)
79 list(validate_message(pyin, 'pyin', msg_id))
75 list(validate_message(pyin, 'pyin', msg_id))
80 nt.assert_equal(pyin['content']['code'], code)
76 nt.assert_equal(pyin['content']['code'], code)
81
77
@@ -192,7 +188,7 b' class ArgSpec(Reference):'
192
188
193
189
194 class Status(Reference):
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 class CompleteReply(Reference):
194 class CompleteReply(Reference):
@@ -301,9 +297,8 b' def validate_message(msg, msg_type=None, parent=None):'
301 def test_execute():
297 def test_execute():
302 flush_channels()
298 flush_channels()
303
299
304 shell = KM.shell_channel
300 msg_id = KC.execute(code='x=1')
305 msg_id = shell.execute(code='x=1')
301 reply = KC.get_shell_msg(timeout=2)
306 reply = shell.get_msg(timeout=2)
307 for tst in validate_message(reply, 'execute_reply', msg_id):
302 for tst in validate_message(reply, 'execute_reply', msg_id):
308 yield tst
303 yield tst
309
304
@@ -314,23 +309,23 b' def test_execute_silent():'
314 msg_id, reply = execute(code='x=1', silent=True)
309 msg_id, reply = execute(code='x=1', silent=True)
315
310
316 # flush status=idle
311 # flush status=idle
317 status = KM.iopub_channel.get_msg(timeout=2)
312 status = KC.iopub_channel.get_msg(timeout=2)
318 for tst in validate_message(status, 'status', msg_id):
313 for tst in validate_message(status, 'status', msg_id):
319 yield tst
314 yield tst
320 nt.assert_equal(status['content']['execution_state'], 'idle')
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 count = reply['execution_count']
318 count = reply['execution_count']
324
319
325 msg_id, reply = execute(code='x=2', silent=True)
320 msg_id, reply = execute(code='x=2', silent=True)
326
321
327 # flush status=idle
322 # flush status=idle
328 status = KM.iopub_channel.get_msg(timeout=2)
323 status = KC.iopub_channel.get_msg(timeout=2)
329 for tst in validate_message(status, 'status', msg_id):
324 for tst in validate_message(status, 'status', msg_id):
330 yield tst
325 yield tst
331 yield nt.assert_equal(status['content']['execution_state'], 'idle')
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 count_2 = reply['execution_count']
329 count_2 = reply['execution_count']
335 yield nt.assert_equal(count_2, count)
330 yield nt.assert_equal(count_2, count)
336
331
@@ -343,7 +338,7 b' def test_execute_error():'
343 yield nt.assert_equal(reply['status'], 'error')
338 yield nt.assert_equal(reply['status'], 'error')
344 yield nt.assert_equal(reply['ename'], 'ZeroDivisionError')
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 for tst in validate_message(pyerr, 'pyerr', msg_id):
342 for tst in validate_message(pyerr, 'pyerr', msg_id):
348 yield tst
343 yield tst
349
344
@@ -382,10 +377,8 b' def test_user_expressions():'
382 def test_oinfo():
377 def test_oinfo():
383 flush_channels()
378 flush_channels()
384
379
385 shell = KM.shell_channel
380 msg_id = KC.object_info('a')
386
381 reply = KC.get_shell_msg(timeout=2)
387 msg_id = shell.object_info('a')
388 reply = shell.get_msg(timeout=2)
389 for tst in validate_message(reply, 'object_info_reply', msg_id):
382 for tst in validate_message(reply, 'object_info_reply', msg_id):
390 yield tst
383 yield tst
391
384
@@ -394,12 +387,10 b' def test_oinfo():'
394 def test_oinfo_found():
387 def test_oinfo_found():
395 flush_channels()
388 flush_channels()
396
389
397 shell = KM.shell_channel
398
399 msg_id, reply = execute(code='a=5')
390 msg_id, reply = execute(code='a=5')
400
391
401 msg_id = shell.object_info('a')
392 msg_id = KC.object_info('a')
402 reply = shell.get_msg(timeout=2)
393 reply = KC.get_shell_msg(timeout=2)
403 for tst in validate_message(reply, 'object_info_reply', msg_id):
394 for tst in validate_message(reply, 'object_info_reply', msg_id):
404 yield tst
395 yield tst
405 content = reply['content']
396 content = reply['content']
@@ -412,12 +403,10 b' def test_oinfo_found():'
412 def test_oinfo_detail():
403 def test_oinfo_detail():
413 flush_channels()
404 flush_channels()
414
405
415 shell = KM.shell_channel
416
417 msg_id, reply = execute(code='ip=get_ipython()')
406 msg_id, reply = execute(code='ip=get_ipython()')
418
407
419 msg_id = shell.object_info('ip.object_inspect', detail_level=2)
408 msg_id = KC.object_info('ip.object_inspect', detail_level=2)
420 reply = shell.get_msg(timeout=2)
409 reply = KC.get_shell_msg(timeout=2)
421 for tst in validate_message(reply, 'object_info_reply', msg_id):
410 for tst in validate_message(reply, 'object_info_reply', msg_id):
422 yield tst
411 yield tst
423 content = reply['content']
412 content = reply['content']
@@ -431,10 +420,8 b' def test_oinfo_detail():'
431 def test_oinfo_not_found():
420 def test_oinfo_not_found():
432 flush_channels()
421 flush_channels()
433
422
434 shell = KM.shell_channel
423 msg_id = KC.object_info('dne')
435
424 reply = KC.get_shell_msg(timeout=2)
436 msg_id = shell.object_info('dne')
437 reply = shell.get_msg(timeout=2)
438 for tst in validate_message(reply, 'object_info_reply', msg_id):
425 for tst in validate_message(reply, 'object_info_reply', msg_id):
439 yield tst
426 yield tst
440 content = reply['content']
427 content = reply['content']
@@ -445,12 +432,10 b' def test_oinfo_not_found():'
445 def test_complete():
432 def test_complete():
446 flush_channels()
433 flush_channels()
447
434
448 shell = KM.shell_channel
449
450 msg_id, reply = execute(code="alpha = albert = 5")
435 msg_id, reply = execute(code="alpha = albert = 5")
451
436
452 msg_id = shell.complete('al', 'al', 2)
437 msg_id = KC.complete('al', 'al', 2)
453 reply = shell.get_msg(timeout=2)
438 reply = KC.get_shell_msg(timeout=2)
454 for tst in validate_message(reply, 'complete_reply', msg_id):
439 for tst in validate_message(reply, 'complete_reply', msg_id):
455 yield tst
440 yield tst
456 matches = reply['content']['matches']
441 matches = reply['content']['matches']
@@ -462,10 +447,8 b' def test_complete():'
462 def test_kernel_info_request():
447 def test_kernel_info_request():
463 flush_channels()
448 flush_channels()
464
449
465 shell = KM.shell_channel
450 msg_id = KC.kernel_info()
466
451 reply = KC.get_shell_msg(timeout=2)
467 msg_id = shell.kernel_info()
468 reply = shell.get_msg(timeout=2)
469 for tst in validate_message(reply, 'kernel_info_reply', msg_id):
452 for tst in validate_message(reply, 'kernel_info_reply', msg_id):
470 yield tst
453 yield tst
471
454
@@ -479,7 +462,7 b' def test_stream():'
479
462
480 msg_id, reply = execute("print('hi')")
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 for tst in validate_message(stdout, 'stream', msg_id):
466 for tst in validate_message(stdout, 'stream', msg_id):
484 yield tst
467 yield tst
485 content = stdout['content']
468 content = stdout['content']
@@ -493,7 +476,7 b' def test_display_data():'
493
476
494 msg_id, reply = execute("from IPython.core.display import display; display(1)")
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 for tst in validate_message(display, 'display_data', parent=msg_id):
480 for tst in validate_message(display, 'display_data', parent=msg_id):
498 yield tst
481 yield tst
499 data = display['content']['data']
482 data = display['content']['data']
@@ -8,13 +8,15 b' from IPython.testing import decorators as dec'
8
8
9 from IPython.config.loader import Config
9 from IPython.config.loader import Config
10 from IPython.utils.localinterfaces import LOCALHOST
10 from IPython.utils.localinterfaces import LOCALHOST
11 from IPython.kernel.kernelmanager import KernelManager
11 from IPython.kernel import KernelManager
12 from IPython.kernel.multikernelmanager import MultiKernelManager
12 from IPython.kernel.multikernelmanager import MultiKernelManager
13
13
14 class TestKernelManager(TestCase):
14 class TestKernelManager(TestCase):
15
15
16 def _get_tcp_km(self):
16 def _get_tcp_km(self):
17 return MultiKernelManager()
17 c = Config()
18 km = MultiKernelManager(config=c)
19 return km
18
20
19 def _get_ipc_km(self):
21 def _get_ipc_km(self):
20 c = Config()
22 c = Config()
@@ -25,10 +27,12 b' class TestKernelManager(TestCase):'
25
27
26 def _run_lifecycle(self, km):
28 def _run_lifecycle(self, km):
27 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
29 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
30 self.assertTrue(km.is_alive(kid))
28 self.assertTrue(kid in km)
31 self.assertTrue(kid in km)
29 self.assertTrue(kid in km.list_kernel_ids())
32 self.assertTrue(kid in km.list_kernel_ids())
30 self.assertEqual(len(km),1)
33 self.assertEqual(len(km),1)
31 km.restart_kernel(kid)
34 km.restart_kernel(kid)
35 self.assertTrue(km.is_alive(kid))
32 self.assertTrue(kid in km.list_kernel_ids())
36 self.assertTrue(kid in km.list_kernel_ids())
33 # We need a delay here to give the restarting kernel a chance to
37 # We need a delay here to give the restarting kernel a chance to
34 # restart. Otherwise, the interrupt will kill it, causing the test
38 # restart. Otherwise, the interrupt will kill it, causing the test
@@ -51,13 +55,13 b' class TestKernelManager(TestCase):'
51 self.assertEqual(ip, cinfo['ip'])
55 self.assertEqual(ip, cinfo['ip'])
52 self.assertTrue('stdin_port' in cinfo)
56 self.assertTrue('stdin_port' in cinfo)
53 self.assertTrue('iopub_port' in cinfo)
57 self.assertTrue('iopub_port' in cinfo)
54 stream = km.create_iopub_stream(kid)
58 stream = km.connect_iopub(kid)
55 stream.close()
59 stream.close()
56 self.assertTrue('shell_port' in cinfo)
60 self.assertTrue('shell_port' in cinfo)
57 stream = km.create_shell_stream(kid)
61 stream = km.connect_shell(kid)
58 stream.close()
62 stream.close()
59 self.assertTrue('hb_port' in cinfo)
63 self.assertTrue('hb_port' in cinfo)
60 stream = km.create_hb_stream(kid)
64 stream = km.connect_hb(kid)
61 stream.close()
65 stream.close()
62 km.shutdown_kernel(kid)
66 km.shutdown_kernel(kid)
63
67
@@ -25,11 +25,17 b' from IPython import kernel'
25
25
26 @dec.parametric
26 @dec.parametric
27 def test_kms():
27 def test_kms():
28 for base in ("", "Blocking", "Multi"):
28 for base in ("", "Multi"):
29 KM = base + "KernelManager"
29 KM = base + "KernelManager"
30 yield nt.assert_true(KM in dir(kernel), KM)
30 yield nt.assert_true(KM in dir(kernel), KM)
31
31
32 @dec.parametric
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 def test_launcher():
39 def test_launcher():
34 for name in launcher.__all__:
40 for name in launcher.__all__:
35 yield nt.assert_true(name in dir(kernel), name)
41 yield nt.assert_true(name in dir(kernel), name)
@@ -275,6 +275,9 b' class Kernel(Configurable):'
275
275
276 for s in self.shell_streams:
276 for s in self.shell_streams:
277 s.on_recv(make_dispatcher(s), copy=False)
277 s.on_recv(make_dispatcher(s), copy=False)
278
279 # publish idle status
280 self._publish_status('starting')
278
281
279 def do_one_iteration(self):
282 def do_one_iteration(self):
280 """step eventloop just once"""
283 """step eventloop just once"""
@@ -69,6 +69,7 b' kernel_aliases.update({'
69 'shell' : 'IPKernelApp.shell_port',
69 'shell' : 'IPKernelApp.shell_port',
70 'iopub' : 'IPKernelApp.iopub_port',
70 'iopub' : 'IPKernelApp.iopub_port',
71 'stdin' : 'IPKernelApp.stdin_port',
71 'stdin' : 'IPKernelApp.stdin_port',
72 'control' : 'IPKernelApp.control_port',
72 'f' : 'IPKernelApp.connection_file',
73 'f' : 'IPKernelApp.connection_file',
73 'parent': 'IPKernelApp.parent',
74 'parent': 'IPKernelApp.parent',
74 'transport': 'IPKernelApp.transport',
75 'transport': 'IPKernelApp.transport',
@@ -145,7 +146,8 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
145 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
146 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
146 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
147 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
147 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
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 connection_file = Unicode('', config=True,
151 connection_file = Unicode('', config=True,
150 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
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 if self.ip == self._ip_default() and 'ip' in cfg:
229 if self.ip == self._ip_default() and 'ip' in cfg:
228 # not overridden by config or cl_args
230 # not overridden by config or cl_args
229 self.ip = cfg['ip']
231 self.ip = cfg['ip']
230 for channel in ('hb', 'shell', 'iopub', 'stdin'):
232 for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
231 name = channel + '_port'
233 name = channel + '_port'
232 if getattr(self, name) == 0 and name in cfg:
234 if getattr(self, name) == 0 and name in cfg:
233 # not overridden by config or cl_args
235 # not overridden by config or cl_args
@@ -241,7 +243,7 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
241 self.log.debug("Writing connection file: %s", cf)
243 self.log.debug("Writing connection file: %s", cf)
242 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
244 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
243 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
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 def cleanup_connection_file(self):
248 def cleanup_connection_file(self):
247 cf = self.abs_connection_file
249 cf = self.abs_connection_file
@@ -257,7 +259,7 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
257 """cleanup ipc files if we wrote them"""
259 """cleanup ipc files if we wrote them"""
258 if self.transport != 'ipc':
260 if self.transport != 'ipc':
259 return
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 ipcfile = "%s-%i" % (self.ip, port)
263 ipcfile = "%s-%i" % (self.ip, port)
262 try:
264 try:
263 os.remove(ipcfile)
265 os.remove(ipcfile)
@@ -282,15 +284,19 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
282
284
283 self.shell_socket = context.socket(zmq.ROUTER)
285 self.shell_socket = context.socket(zmq.ROUTER)
284 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
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 self.iopub_socket = context.socket(zmq.PUB)
289 self.iopub_socket = context.socket(zmq.PUB)
288 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
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 self.stdin_socket = context.socket(zmq.ROUTER)
293 self.stdin_socket = context.socket(zmq.ROUTER)
292 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
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 def init_heartbeat(self):
301 def init_heartbeat(self):
296 """start the heart beating"""
302 """start the heart beating"""
@@ -299,7 +305,7 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
299 hb_ctx = zmq.Context()
305 hb_ctx = zmq.Context()
300 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
306 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
301 self.hb_port = self.heartbeat.port
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 self.heartbeat.start()
309 self.heartbeat.start()
304
310
305 # Helper to make it easier to connect to an existing kernel.
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 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
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 def init_session(self):
333 def init_session(self):
327 """create our session object"""
334 """create our session object"""
@@ -353,11 +360,12 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
353 def init_kernel(self):
360 def init_kernel(self):
354 """Create the Kernel object itself"""
361 """Create the Kernel object itself"""
355 shell_stream = ZMQStream(self.shell_socket)
362 shell_stream = ZMQStream(self.shell_socket)
363 control_stream = ZMQStream(self.control_socket)
356
364
357 kernel_factory = import_item(str(self.kernel_class))
365 kernel_factory = import_item(str(self.kernel_class))
358
366
359 kernel = kernel_factory(config=self.config, session=self.session,
367 kernel = kernel_factory(config=self.config, session=self.session,
360 shell_streams=[shell_stream],
368 shell_streams=[shell_stream, control_stream],
361 iopub_socket=self.iopub_socket,
369 iopub_socket=self.iopub_socket,
362 stdin_socket=self.stdin_socket,
370 stdin_socket=self.stdin_socket,
363 log=self.log,
371 log=self.log,
@@ -22,7 +22,7 b' from subprocess import Popen, PIPE'
22
22
23 import nose.tools as nt
23 import nose.tools as nt
24
24
25 from IPython.kernel.blockingkernelmanager import BlockingKernelManager
25 from IPython.kernel import BlockingKernelClient
26 from IPython.utils import path, py3compat
26 from IPython.utils import path, py3compat
27
27
28 #-------------------------------------------------------------------------------
28 #-------------------------------------------------------------------------------
@@ -83,14 +83,14 b' def setup_kernel(cmd):'
83 kernel.terminate()
83 kernel.terminate()
84 raise IOError("Connection file %r never arrived" % connection_file)
84 raise IOError("Connection file %r never arrived" % connection_file)
85
85
86 km = BlockingKernelManager(connection_file=connection_file)
86 client = BlockingKernelClient(connection_file=connection_file)
87 km.load_connection_file()
87 client.load_connection_file()
88 km.start_channels()
88 client.start_channels()
89
89
90 try:
90 try:
91 yield km
91 yield client
92 finally:
92 finally:
93 km.stop_channels()
93 client.stop_channels()
94 kernel.terminate()
94 kernel.terminate()
95
95
96 def test_embed_kernel_basic():
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:
108 with setup_kernel(cmd) as client:
109 shell = km.shell_channel
110
111 # oinfo a (int)
109 # oinfo a (int)
112 msg_id = shell.object_info('a')
110 msg_id = client.object_info('a')
113 msg = shell.get_msg(block=True, timeout=2)
111 msg = client.get_shell_msg(block=True, timeout=2)
114 content = msg['content']
112 content = msg['content']
115 nt.assert_true(content['found'])
113 nt.assert_true(content['found'])
116
114
117 msg_id = shell.execute("c=a*2")
115 msg_id = client.execute("c=a*2")
118 msg = shell.get_msg(block=True, timeout=2)
116 msg = client.get_shell_msg(block=True, timeout=2)
119 content = msg['content']
117 content = msg['content']
120 nt.assert_equal(content['status'], u'ok')
118 nt.assert_equal(content['status'], u'ok')
121
119
122 # oinfo c (should be 10)
120 # oinfo c (should be 10)
123 msg_id = shell.object_info('c')
121 msg_id = client.object_info('c')
124 msg = shell.get_msg(block=True, timeout=2)
122 msg = client.get_shell_msg(block=True, timeout=2)
125 content = msg['content']
123 content = msg['content']
126 nt.assert_true(content['found'])
124 nt.assert_true(content['found'])
127 nt.assert_equal(content['string_form'], u'10')
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:
139 with setup_kernel(cmd) as client:
142 shell = km.shell_channel
143
144 # oinfo a (int)
140 # oinfo a (int)
145 msg_id = shell.object_info('a')
141 msg_id = client.object_info('a')
146 msg = shell.get_msg(block=True, timeout=2)
142 msg = client.get_shell_msg(block=True, timeout=2)
147 content = msg['content']
143 content = msg['content']
148 nt.assert_true(content['found'])
144 nt.assert_true(content['found'])
149 nt.assert_equal(content['string_form'], u'5')
145 nt.assert_equal(content['string_form'], u'5')
150
146
151 # oinfo b (str)
147 # oinfo b (str)
152 msg_id = shell.object_info('b')
148 msg_id = client.object_info('b')
153 msg = shell.get_msg(block=True, timeout=2)
149 msg = client.get_shell_msg(block=True, timeout=2)
154 content = msg['content']
150 content = msg['content']
155 nt.assert_true(content['found'])
151 nt.assert_true(content['found'])
156 nt.assert_equal(content['string_form'], u'hi there')
152 nt.assert_equal(content['string_form'], u'hi there')
157
153
158 # oinfo c (undefined)
154 # oinfo c (undefined)
159 msg_id = shell.object_info('c')
155 msg_id = client.object_info('c')
160 msg = shell.get_msg(block=True, timeout=2)
156 msg = client.get_shell_msg(block=True, timeout=2)
161 content = msg['content']
157 content = msg['content']
162 nt.assert_false(content['found'])
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:
175 with setup_kernel(cmd) as client:
180 shell = km.shell_channel
181 for i in range(5):
176 for i in range(5):
182 msg_id = shell.object_info('count')
177 msg_id = client.object_info('count')
183 msg = shell.get_msg(block=True, timeout=2)
178 msg = client.get_shell_msg(block=True, timeout=2)
184 content = msg['content']
179 content = msg['content']
185 nt.assert_true(content['found'])
180 nt.assert_true(content['found'])
186 nt.assert_equal(content['string_form'], unicode(i))
181 nt.assert_equal(content['string_form'], unicode(i))
187
182
188 # exit from embed_kernel
183 # exit from embed_kernel
189 shell.execute("get_ipython().exit_now = True")
184 client.execute("get_ipython().exit_now = True")
190 msg = shell.get_msg(block=True, timeout=2)
185 msg = client.get_shell_msg(block=True, timeout=2)
191 time.sleep(0.2)
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 the notion of a prompt string that allowed arbitrary code to be evaluated, and
195 the notion of a prompt string that allowed arbitrary code to be evaluated, and
196 this was put to good use by many in creating prompts that displayed system
196 this was put to good use by many in creating prompts that displayed system
197 status, path information, and even more esoteric uses like remote instrument
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 between the kernel and the clients, the kernel has no prompt knowledge; prompts
199 between the kernel and the clients, the kernel has no prompt knowledge; prompts
200 are a frontend-side feature, and it should be even possible for different
200 are a frontend-side feature, and it should be even possible for different
201 frontends to display different prompts while interacting with the same kernel.
201 frontends to display different prompts while interacting with the same kernel.
@@ -934,7 +934,8 b' Message type: ``status``::'
934 content = {
934 content = {
935 # When the kernel starts to execute code, it will enter the 'busy'
935 # When the kernel starts to execute code, it will enter the 'busy'
936 # state and when it finishes, it will enter the 'idle' state.
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 Kernel crashes
941 Kernel crashes
General Comments 0
You need to be logged in to leave comments. Login now