##// END OF EJS Templates
Fixing bug in kernelmanager tests that was causing tests to hang....
Brian E. Granger -
Show More
@@ -1,60 +1,69 b''
1 """Tests for the notebook kernel and session manager."""
1 """Tests for the notebook kernel and session manager."""
2
2
3 import time
3 from unittest import TestCase
4 from unittest import TestCase
4
5
5 from IPython.config.loader import Config
6 from IPython.config.loader import Config
6 from IPython.frontend.html.notebook.kernelmanager import MultiKernelManager
7 from IPython.frontend.html.notebook.kernelmanager import MultiKernelManager
7 from IPython.zmq.kernelmanager import KernelManager
8 from IPython.zmq.kernelmanager import KernelManager
8
9
9 class TestKernelManager(TestCase):
10 class TestKernelManager(TestCase):
10
11
11 def _get_tcp_km(self):
12 def _get_tcp_km(self):
12 return MultiKernelManager()
13 return MultiKernelManager()
13
14
14 def _get_ipc_km(self):
15 def _get_ipc_km(self):
15 c = Config()
16 c = Config()
16 c.KernelManager.transport = 'ipc'
17 c.KernelManager.transport = 'ipc'
17 c.KernelManager.ip = 'test'
18 c.KernelManager.ip = 'test'
18 km = MultiKernelManager(config=c)
19 km = MultiKernelManager(config=c)
19 return km
20 return km
20
21
21 def _run_lifecycle(self, km):
22 def _run_lifecycle(self, km):
22 kid = km.start_kernel()
23 kid = km.start_kernel()
23 self.assertTrue(kid in km)
24 self.assertTrue(kid in km)
24 self.assertTrue(kid in km.list_kernel_ids())
25 self.assertTrue(kid in km.list_kernel_ids())
25 self.assertEqual(len(km),1)
26 self.assertEqual(len(km),1)
26 km.restart_kernel(kid)
27 km.restart_kernel(kid)
27 self.assertTrue(kid in km.list_kernel_ids())
28 self.assertTrue(kid in km.list_kernel_ids())
29 # We need a delay here to give the restarting kernel a chance to
30 # restart. Otherwise, the interrupt will kill it, causing the test
31 # suite to hang. The reason it *hangs* is that the shutdown
32 # message for the restart sometimes hasn't been sent to the kernel.
33 # Because linger is oo on the shell channel, the context can't
34 # close until the message is sent to the kernel, which is not dead.
35 time.sleep(1.0)
28 km.interrupt_kernel(kid)
36 km.interrupt_kernel(kid)
29 k = km.get_kernel(kid)
37 k = km.get_kernel(kid)
30 self.assertTrue(isinstance(k, KernelManager))
38 self.assertTrue(isinstance(k, KernelManager))
31 km.shutdown_kernel(kid)
39 km.shutdown_kernel(kid)
32 self.assertTrue(not kid in km)
40 self.assertTrue(not kid in km)
33
41
34 def _run_cinfo(self, km, transport, ip):
42 def _run_cinfo(self, km, transport, ip):
35 kid = km.start_kernel()
43 kid = km.start_kernel()
36 k = km.get_kernel(kid)
44 k = km.get_kernel(kid)
37 cinfo = km.get_connection_info(kid)
45 cinfo = km.get_connection_info(kid)
38 self.assertEqual(transport, cinfo['transport'])
46 self.assertEqual(transport, cinfo['transport'])
39 self.assertEqual(ip, cinfo['ip'])
47 self.assertEqual(ip, cinfo['ip'])
40 self.assertTrue('stdin_port' in cinfo)
48 self.assertTrue('stdin_port' in cinfo)
41 self.assertTrue('iopub_port' in cinfo)
49 self.assertTrue('iopub_port' in cinfo)
42 self.assertTrue('shell_port' in cinfo)
50 self.assertTrue('shell_port' in cinfo)
43 self.assertTrue('hb_port' in cinfo)
51 self.assertTrue('hb_port' in cinfo)
44 km.shutdown_kernel(kid)
52 km.shutdown_kernel(kid)
45
53
46 def test_km_tcp(self):
54 def test_tcp_lifecycle(self):
47 km = self._get_tcp_km()
55 km = self._get_tcp_km()
48 self._run_lifecycle(km)
56 self._run_lifecycle(km)
49
57
50 def test_tcp_cinfo(self):
58 def test_tcp_cinfo(self):
51 km = self._get_tcp_km()
59 km = self._get_tcp_km()
52 self._run_cinfo(km, 'tcp', '127.0.0.1')
60 self._run_cinfo(km, 'tcp', '127.0.0.1')
53
61
54 def test_km_ipc(self):
62 def test_ipc_lifecycle(self):
55 km = self._get_ipc_km()
63 km = self._get_ipc_km()
56 self._run_lifecycle(km)
64 self._run_lifecycle(km)
57
65
58 def test_ipc_cinfo(self):
66 def test_ipc_cinfo(self):
59 km = self._get_ipc_km()
67 km = self._get_ipc_km()
60 self._run_cinfo(km, 'ipc', 'test')
68 self._run_cinfo(km, 'ipc', 'test')
69
@@ -1,1083 +1,1082 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 TODO
3 TODO
4 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2011 The IPython Development Team
8 # Copyright (C) 2008-2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import atexit
19 import atexit
20 import errno
20 import errno
21 import json
21 import json
22 from subprocess import Popen
22 from subprocess import Popen
23 import os
23 import os
24 import signal
24 import signal
25 import sys
25 import sys
26 from threading import Thread
26 from threading import Thread
27 import time
27 import time
28
28
29 # System library imports.
29 # System library imports.
30 import zmq
30 import zmq
31 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
31 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
32 # during garbage collection of threads at exit:
32 # during garbage collection of threads at exit:
33 from zmq import ZMQError
33 from zmq import ZMQError
34 from zmq.eventloop import ioloop, zmqstream
34 from zmq.eventloop import ioloop, zmqstream
35
35
36 # Local imports.
36 # Local imports.
37 from IPython.config.configurable import Configurable
37 from IPython.config.configurable import Configurable
38 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
38 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
39 from IPython.utils.traitlets import (
39 from IPython.utils.traitlets import (
40 Any, Instance, Type, Unicode, Integer, Bool, CaselessStrEnum
40 Any, Instance, Type, Unicode, Integer, Bool, CaselessStrEnum
41 )
41 )
42 from IPython.utils.py3compat import str_to_bytes
42 from IPython.utils.py3compat import str_to_bytes
43 from IPython.zmq.entry_point import write_connection_file
43 from IPython.zmq.entry_point import write_connection_file
44 from session import Session
44 from session import Session
45 from IPython.zmq.kernelmanagerabc import (
45 from IPython.zmq.kernelmanagerabc import (
46 ShellChannelABC, IOPubChannelABC,
46 ShellChannelABC, IOPubChannelABC,
47 HBChannelABC, StdInChannelABC,
47 HBChannelABC, StdInChannelABC,
48 KernelManagerABC
48 KernelManagerABC
49 )
49 )
50
50
51
51
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 # Constants and exceptions
53 # Constants and exceptions
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55
55
56 class InvalidPortNumber(Exception):
56 class InvalidPortNumber(Exception):
57 pass
57 pass
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Utility functions
60 # Utility functions
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63 # some utilities to validate message structure, these might get moved elsewhere
63 # some utilities to validate message structure, these might get moved elsewhere
64 # if they prove to have more generic utility
64 # if they prove to have more generic utility
65
65
66 def validate_string_list(lst):
66 def validate_string_list(lst):
67 """Validate that the input is a list of strings.
67 """Validate that the input is a list of strings.
68
68
69 Raises ValueError if not."""
69 Raises ValueError if not."""
70 if not isinstance(lst, list):
70 if not isinstance(lst, list):
71 raise ValueError('input %r must be a list' % lst)
71 raise ValueError('input %r must be a list' % lst)
72 for x in lst:
72 for x in lst:
73 if not isinstance(x, basestring):
73 if not isinstance(x, basestring):
74 raise ValueError('element %r in list must be a string' % x)
74 raise ValueError('element %r in list must be a string' % x)
75
75
76
76
77 def validate_string_dict(dct):
77 def validate_string_dict(dct):
78 """Validate that the input is a dict with string keys and values.
78 """Validate that the input is a dict with string keys and values.
79
79
80 Raises ValueError if not."""
80 Raises ValueError if not."""
81 for k,v in dct.iteritems():
81 for k,v in dct.iteritems():
82 if not isinstance(k, basestring):
82 if not isinstance(k, basestring):
83 raise ValueError('key %r in dict must be a string' % k)
83 raise ValueError('key %r in dict must be a string' % k)
84 if not isinstance(v, basestring):
84 if not isinstance(v, basestring):
85 raise ValueError('value %r in dict must be a string' % v)
85 raise ValueError('value %r in dict must be a string' % v)
86
86
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # ZMQ Socket Channel classes
89 # ZMQ Socket Channel classes
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92 class ZMQSocketChannel(Thread):
92 class ZMQSocketChannel(Thread):
93 """The base class for the channels that use ZMQ sockets."""
93 """The base class for the channels that use ZMQ sockets."""
94 context = None
94 context = None
95 session = None
95 session = None
96 socket = None
96 socket = None
97 ioloop = None
97 ioloop = None
98 stream = None
98 stream = None
99 _address = None
99 _address = None
100 _exiting = False
100 _exiting = False
101
101
102 def __init__(self, context, session, address):
102 def __init__(self, context, session, address):
103 """Create a channel.
103 """Create a channel.
104
104
105 Parameters
105 Parameters
106 ----------
106 ----------
107 context : :class:`zmq.Context`
107 context : :class:`zmq.Context`
108 The ZMQ context to use.
108 The ZMQ context to use.
109 session : :class:`session.Session`
109 session : :class:`session.Session`
110 The session to use.
110 The session to use.
111 address : zmq url
111 address : zmq url
112 Standard (ip, port) tuple that the kernel is listening on.
112 Standard (ip, port) tuple that the kernel is listening on.
113 """
113 """
114 super(ZMQSocketChannel, self).__init__()
114 super(ZMQSocketChannel, self).__init__()
115 self.daemon = True
115 self.daemon = True
116
116
117 self.context = context
117 self.context = context
118 self.session = session
118 self.session = session
119 if isinstance(address, tuple):
119 if isinstance(address, tuple):
120 if address[1] == 0:
120 if address[1] == 0:
121 message = 'The port number for a channel cannot be 0.'
121 message = 'The port number for a channel cannot be 0.'
122 raise InvalidPortNumber(message)
122 raise InvalidPortNumber(message)
123 address = "tcp://%s:%i" % address
123 address = "tcp://%s:%i" % address
124 self._address = address
124 self._address = address
125 atexit.register(self._notice_exit)
125 atexit.register(self._notice_exit)
126
126
127 def _notice_exit(self):
127 def _notice_exit(self):
128 self._exiting = True
128 self._exiting = True
129
129
130 def _run_loop(self):
130 def _run_loop(self):
131 """Run my loop, ignoring EINTR events in the poller"""
131 """Run my loop, ignoring EINTR events in the poller"""
132 while True:
132 while True:
133 try:
133 try:
134 self.ioloop.start()
134 self.ioloop.start()
135 except ZMQError as e:
135 except ZMQError as e:
136 if e.errno == errno.EINTR:
136 if e.errno == errno.EINTR:
137 continue
137 continue
138 else:
138 else:
139 raise
139 raise
140 except Exception:
140 except Exception:
141 if self._exiting:
141 if self._exiting:
142 break
142 break
143 else:
143 else:
144 raise
144 raise
145 else:
145 else:
146 break
146 break
147
147
148 def stop(self):
148 def stop(self):
149 """Stop the channel's event loop and join its thread.
149 """Stop the channel's event loop and join its thread.
150
150
151 This calls :method:`Thread.join` and returns when the thread
151 This calls :method:`Thread.join` and returns when the thread
152 terminates. :class:`RuntimeError` will be raised if
152 terminates. :class:`RuntimeError` will be raised if
153 :method:`self.start` is called again.
153 :method:`self.start` is called again.
154 """
154 """
155 self.join()
155 self.join()
156
156
157 @property
157 @property
158 def address(self):
158 def address(self):
159 """Get the channel's address as a zmq url string.
159 """Get the channel's address as a zmq url string.
160
160
161 These URLS have the form: 'tcp://127.0.0.1:5555'.
161 These URLS have the form: 'tcp://127.0.0.1:5555'.
162 """
162 """
163 return self._address
163 return self._address
164
164
165 def _queue_send(self, msg):
165 def _queue_send(self, msg):
166 """Queue a message to be sent from the IOLoop's thread.
166 """Queue a message to be sent from the IOLoop's thread.
167
167
168 Parameters
168 Parameters
169 ----------
169 ----------
170 msg : message to send
170 msg : message to send
171
171
172 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
172 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
173 thread control of the action.
173 thread control of the action.
174 """
174 """
175 def thread_send():
175 def thread_send():
176 self.session.send(self.stream, msg)
176 self.session.send(self.stream, msg)
177 self.ioloop.add_callback(thread_send)
177 self.ioloop.add_callback(thread_send)
178
178
179 def _handle_recv(self, msg):
179 def _handle_recv(self, msg):
180 """Callback for stream.on_recv.
180 """Callback for stream.on_recv.
181
181
182 Unpacks message, and calls handlers with it.
182 Unpacks message, and calls handlers with it.
183 """
183 """
184 ident,smsg = self.session.feed_identities(msg)
184 ident,smsg = self.session.feed_identities(msg)
185 self.call_handlers(self.session.unserialize(smsg))
185 self.call_handlers(self.session.unserialize(smsg))
186
186
187
187
188
188
189 class ShellChannel(ZMQSocketChannel):
189 class ShellChannel(ZMQSocketChannel):
190 """The shell channel for issuing request/replies to the kernel."""
190 """The shell channel for issuing request/replies to the kernel."""
191
191
192 command_queue = None
192 command_queue = None
193 # flag for whether execute requests should be allowed to call raw_input:
193 # flag for whether execute requests should be allowed to call raw_input:
194 allow_stdin = True
194 allow_stdin = True
195
195
196 def __init__(self, context, session, address):
196 def __init__(self, context, session, address):
197 super(ShellChannel, self).__init__(context, session, address)
197 super(ShellChannel, self).__init__(context, session, address)
198 self.ioloop = ioloop.IOLoop()
198 self.ioloop = ioloop.IOLoop()
199
199
200 def run(self):
200 def run(self):
201 """The thread's main activity. Call start() instead."""
201 """The thread's main activity. Call start() instead."""
202 self.socket = self.context.socket(zmq.DEALER)
202 self.socket = self.context.socket(zmq.DEALER)
203 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
203 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
204 self.socket.linger = 0
205 self.socket.connect(self.address)
204 self.socket.connect(self.address)
206 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
205 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
207 self.stream.on_recv(self._handle_recv)
206 self.stream.on_recv(self._handle_recv)
208 self._run_loop()
207 self._run_loop()
209 try:
208 try:
210 self.socket.close()
209 self.socket.close()
211 except:
210 except:
212 pass
211 pass
213
212
214 def stop(self):
213 def stop(self):
215 """Stop the channel's event loop and join its thread."""
214 """Stop the channel's event loop and join its thread."""
216 self.ioloop.stop()
215 self.ioloop.stop()
217 super(ShellChannel, self).stop()
216 super(ShellChannel, self).stop()
218
217
219 def call_handlers(self, msg):
218 def call_handlers(self, msg):
220 """This method is called in the ioloop thread when a message arrives.
219 """This method is called in the ioloop thread when a message arrives.
221
220
222 Subclasses should override this method to handle incoming messages.
221 Subclasses should override this method to handle incoming messages.
223 It is important to remember that this method is called in the thread
222 It is important to remember that this method is called in the thread
224 so that some logic must be done to ensure that the application leve
223 so that some logic must be done to ensure that the application leve
225 handlers are called in the application thread.
224 handlers are called in the application thread.
226 """
225 """
227 raise NotImplementedError('call_handlers must be defined in a subclass.')
226 raise NotImplementedError('call_handlers must be defined in a subclass.')
228
227
229 def execute(self, code, silent=False, store_history=True,
228 def execute(self, code, silent=False, store_history=True,
230 user_variables=None, user_expressions=None, allow_stdin=None):
229 user_variables=None, user_expressions=None, allow_stdin=None):
231 """Execute code in the kernel.
230 """Execute code in the kernel.
232
231
233 Parameters
232 Parameters
234 ----------
233 ----------
235 code : str
234 code : str
236 A string of Python code.
235 A string of Python code.
237
236
238 silent : bool, optional (default False)
237 silent : bool, optional (default False)
239 If set, the kernel will execute the code as quietly possible, and
238 If set, the kernel will execute the code as quietly possible, and
240 will force store_history to be False.
239 will force store_history to be False.
241
240
242 store_history : bool, optional (default True)
241 store_history : bool, optional (default True)
243 If set, the kernel will store command history. This is forced
242 If set, the kernel will store command history. This is forced
244 to be False if silent is True.
243 to be False if silent is True.
245
244
246 user_variables : list, optional
245 user_variables : list, optional
247 A list of variable names to pull from the user's namespace. They
246 A list of variable names to pull from the user's namespace. They
248 will come back as a dict with these names as keys and their
247 will come back as a dict with these names as keys and their
249 :func:`repr` as values.
248 :func:`repr` as values.
250
249
251 user_expressions : dict, optional
250 user_expressions : dict, optional
252 A dict mapping names to expressions to be evaluated in the user's
251 A dict mapping names to expressions to be evaluated in the user's
253 dict. The expression values are returned as strings formatted using
252 dict. The expression values are returned as strings formatted using
254 :func:`repr`.
253 :func:`repr`.
255
254
256 allow_stdin : bool, optional (default self.allow_stdin)
255 allow_stdin : bool, optional (default self.allow_stdin)
257 Flag for whether the kernel can send stdin requests to frontends.
256 Flag for whether the kernel can send stdin requests to frontends.
258
257
259 Some frontends (e.g. the Notebook) do not support stdin requests.
258 Some frontends (e.g. the Notebook) do not support stdin requests.
260 If raw_input is called from code executed from such a frontend, a
259 If raw_input is called from code executed from such a frontend, a
261 StdinNotImplementedError will be raised.
260 StdinNotImplementedError will be raised.
262
261
263 Returns
262 Returns
264 -------
263 -------
265 The msg_id of the message sent.
264 The msg_id of the message sent.
266 """
265 """
267 if user_variables is None:
266 if user_variables is None:
268 user_variables = []
267 user_variables = []
269 if user_expressions is None:
268 if user_expressions is None:
270 user_expressions = {}
269 user_expressions = {}
271 if allow_stdin is None:
270 if allow_stdin is None:
272 allow_stdin = self.allow_stdin
271 allow_stdin = self.allow_stdin
273
272
274
273
275 # Don't waste network traffic if inputs are invalid
274 # Don't waste network traffic if inputs are invalid
276 if not isinstance(code, basestring):
275 if not isinstance(code, basestring):
277 raise ValueError('code %r must be a string' % code)
276 raise ValueError('code %r must be a string' % code)
278 validate_string_list(user_variables)
277 validate_string_list(user_variables)
279 validate_string_dict(user_expressions)
278 validate_string_dict(user_expressions)
280
279
281 # Create class for content/msg creation. Related to, but possibly
280 # Create class for content/msg creation. Related to, but possibly
282 # not in Session.
281 # not in Session.
283 content = dict(code=code, silent=silent, store_history=store_history,
282 content = dict(code=code, silent=silent, store_history=store_history,
284 user_variables=user_variables,
283 user_variables=user_variables,
285 user_expressions=user_expressions,
284 user_expressions=user_expressions,
286 allow_stdin=allow_stdin,
285 allow_stdin=allow_stdin,
287 )
286 )
288 msg = self.session.msg('execute_request', content)
287 msg = self.session.msg('execute_request', content)
289 self._queue_send(msg)
288 self._queue_send(msg)
290 return msg['header']['msg_id']
289 return msg['header']['msg_id']
291
290
292 def complete(self, text, line, cursor_pos, block=None):
291 def complete(self, text, line, cursor_pos, block=None):
293 """Tab complete text in the kernel's namespace.
292 """Tab complete text in the kernel's namespace.
294
293
295 Parameters
294 Parameters
296 ----------
295 ----------
297 text : str
296 text : str
298 The text to complete.
297 The text to complete.
299 line : str
298 line : str
300 The full line of text that is the surrounding context for the
299 The full line of text that is the surrounding context for the
301 text to complete.
300 text to complete.
302 cursor_pos : int
301 cursor_pos : int
303 The position of the cursor in the line where the completion was
302 The position of the cursor in the line where the completion was
304 requested.
303 requested.
305 block : str, optional
304 block : str, optional
306 The full block of code in which the completion is being requested.
305 The full block of code in which the completion is being requested.
307
306
308 Returns
307 Returns
309 -------
308 -------
310 The msg_id of the message sent.
309 The msg_id of the message sent.
311 """
310 """
312 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
311 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
313 msg = self.session.msg('complete_request', content)
312 msg = self.session.msg('complete_request', content)
314 self._queue_send(msg)
313 self._queue_send(msg)
315 return msg['header']['msg_id']
314 return msg['header']['msg_id']
316
315
317 def object_info(self, oname, detail_level=0):
316 def object_info(self, oname, detail_level=0):
318 """Get metadata information about an object in the kernel's namespace.
317 """Get metadata information about an object in the kernel's namespace.
319
318
320 Parameters
319 Parameters
321 ----------
320 ----------
322 oname : str
321 oname : str
323 A string specifying the object name.
322 A string specifying the object name.
324 detail_level : int, optional
323 detail_level : int, optional
325 The level of detail for the introspection (0-2)
324 The level of detail for the introspection (0-2)
326
325
327 Returns
326 Returns
328 -------
327 -------
329 The msg_id of the message sent.
328 The msg_id of the message sent.
330 """
329 """
331 content = dict(oname=oname, detail_level=detail_level)
330 content = dict(oname=oname, detail_level=detail_level)
332 msg = self.session.msg('object_info_request', content)
331 msg = self.session.msg('object_info_request', content)
333 self._queue_send(msg)
332 self._queue_send(msg)
334 return msg['header']['msg_id']
333 return msg['header']['msg_id']
335
334
336 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
335 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
337 """Get entries from the kernel's history list.
336 """Get entries from the kernel's history list.
338
337
339 Parameters
338 Parameters
340 ----------
339 ----------
341 raw : bool
340 raw : bool
342 If True, return the raw input.
341 If True, return the raw input.
343 output : bool
342 output : bool
344 If True, then return the output as well.
343 If True, then return the output as well.
345 hist_access_type : str
344 hist_access_type : str
346 'range' (fill in session, start and stop params), 'tail' (fill in n)
345 'range' (fill in session, start and stop params), 'tail' (fill in n)
347 or 'search' (fill in pattern param).
346 or 'search' (fill in pattern param).
348
347
349 session : int
348 session : int
350 For a range request, the session from which to get lines. Session
349 For a range request, the session from which to get lines. Session
351 numbers are positive integers; negative ones count back from the
350 numbers are positive integers; negative ones count back from the
352 current session.
351 current session.
353 start : int
352 start : int
354 The first line number of a history range.
353 The first line number of a history range.
355 stop : int
354 stop : int
356 The final (excluded) line number of a history range.
355 The final (excluded) line number of a history range.
357
356
358 n : int
357 n : int
359 The number of lines of history to get for a tail request.
358 The number of lines of history to get for a tail request.
360
359
361 pattern : str
360 pattern : str
362 The glob-syntax pattern for a search request.
361 The glob-syntax pattern for a search request.
363
362
364 Returns
363 Returns
365 -------
364 -------
366 The msg_id of the message sent.
365 The msg_id of the message sent.
367 """
366 """
368 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
367 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
369 **kwargs)
368 **kwargs)
370 msg = self.session.msg('history_request', content)
369 msg = self.session.msg('history_request', content)
371 self._queue_send(msg)
370 self._queue_send(msg)
372 return msg['header']['msg_id']
371 return msg['header']['msg_id']
373
372
374 def kernel_info(self):
373 def kernel_info(self):
375 """Request kernel info."""
374 """Request kernel info."""
376 msg = self.session.msg('kernel_info_request')
375 msg = self.session.msg('kernel_info_request')
377 self._queue_send(msg)
376 self._queue_send(msg)
378 return msg['header']['msg_id']
377 return msg['header']['msg_id']
379
378
380 def shutdown(self, restart=False):
379 def shutdown(self, restart=False):
381 """Request an immediate kernel shutdown.
380 """Request an immediate kernel shutdown.
382
381
383 Upon receipt of the (empty) reply, client code can safely assume that
382 Upon receipt of the (empty) reply, client code can safely assume that
384 the kernel has shut down and it's safe to forcefully terminate it if
383 the kernel has shut down and it's safe to forcefully terminate it if
385 it's still alive.
384 it's still alive.
386
385
387 The kernel will send the reply via a function registered with Python's
386 The kernel will send the reply via a function registered with Python's
388 atexit module, ensuring it's truly done as the kernel is done with all
387 atexit module, ensuring it's truly done as the kernel is done with all
389 normal operation.
388 normal operation.
390 """
389 """
391 # Send quit message to kernel. Once we implement kernel-side setattr,
390 # Send quit message to kernel. Once we implement kernel-side setattr,
392 # this should probably be done that way, but for now this will do.
391 # this should probably be done that way, but for now this will do.
393 msg = self.session.msg('shutdown_request', {'restart':restart})
392 msg = self.session.msg('shutdown_request', {'restart':restart})
394 self._queue_send(msg)
393 self._queue_send(msg)
395 return msg['header']['msg_id']
394 return msg['header']['msg_id']
396
395
397
396
398
397
399 class IOPubChannel(ZMQSocketChannel):
398 class IOPubChannel(ZMQSocketChannel):
400 """The iopub channel which listens for messages that the kernel publishes.
399 """The iopub channel which listens for messages that the kernel publishes.
401
400
402 This channel is where all output is published to frontends.
401 This channel is where all output is published to frontends.
403 """
402 """
404
403
405 def __init__(self, context, session, address):
404 def __init__(self, context, session, address):
406 super(IOPubChannel, self).__init__(context, session, address)
405 super(IOPubChannel, self).__init__(context, session, address)
407 self.ioloop = ioloop.IOLoop()
406 self.ioloop = ioloop.IOLoop()
408
407
409 def run(self):
408 def run(self):
410 """The thread's main activity. Call start() instead."""
409 """The thread's main activity. Call start() instead."""
411 self.socket = self.context.socket(zmq.SUB)
410 self.socket = self.context.socket(zmq.SUB)
412 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
411 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
413 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
412 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
414 self.socket.connect(self.address)
413 self.socket.connect(self.address)
415 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
414 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
416 self.stream.on_recv(self._handle_recv)
415 self.stream.on_recv(self._handle_recv)
417 self._run_loop()
416 self._run_loop()
418 try:
417 try:
419 self.socket.close()
418 self.socket.close()
420 except:
419 except:
421 pass
420 pass
422
421
423 def stop(self):
422 def stop(self):
424 """Stop the channel's event loop and join its thread."""
423 """Stop the channel's event loop and join its thread."""
425 self.ioloop.stop()
424 self.ioloop.stop()
426 super(IOPubChannel, self).stop()
425 super(IOPubChannel, self).stop()
427
426
428 def call_handlers(self, msg):
427 def call_handlers(self, msg):
429 """This method is called in the ioloop thread when a message arrives.
428 """This method is called in the ioloop thread when a message arrives.
430
429
431 Subclasses should override this method to handle incoming messages.
430 Subclasses should override this method to handle incoming messages.
432 It is important to remember that this method is called in the thread
431 It is important to remember that this method is called in the thread
433 so that some logic must be done to ensure that the application leve
432 so that some logic must be done to ensure that the application leve
434 handlers are called in the application thread.
433 handlers are called in the application thread.
435 """
434 """
436 raise NotImplementedError('call_handlers must be defined in a subclass.')
435 raise NotImplementedError('call_handlers must be defined in a subclass.')
437
436
438 def flush(self, timeout=1.0):
437 def flush(self, timeout=1.0):
439 """Immediately processes all pending messages on the iopub channel.
438 """Immediately processes all pending messages on the iopub channel.
440
439
441 Callers should use this method to ensure that :method:`call_handlers`
440 Callers should use this method to ensure that :method:`call_handlers`
442 has been called for all messages that have been received on the
441 has been called for all messages that have been received on the
443 0MQ SUB socket of this channel.
442 0MQ SUB socket of this channel.
444
443
445 This method is thread safe.
444 This method is thread safe.
446
445
447 Parameters
446 Parameters
448 ----------
447 ----------
449 timeout : float, optional
448 timeout : float, optional
450 The maximum amount of time to spend flushing, in seconds. The
449 The maximum amount of time to spend flushing, in seconds. The
451 default is one second.
450 default is one second.
452 """
451 """
453 # We do the IOLoop callback process twice to ensure that the IOLoop
452 # We do the IOLoop callback process twice to ensure that the IOLoop
454 # gets to perform at least one full poll.
453 # gets to perform at least one full poll.
455 stop_time = time.time() + timeout
454 stop_time = time.time() + timeout
456 for i in xrange(2):
455 for i in xrange(2):
457 self._flushed = False
456 self._flushed = False
458 self.ioloop.add_callback(self._flush)
457 self.ioloop.add_callback(self._flush)
459 while not self._flushed and time.time() < stop_time:
458 while not self._flushed and time.time() < stop_time:
460 time.sleep(0.01)
459 time.sleep(0.01)
461
460
462 def _flush(self):
461 def _flush(self):
463 """Callback for :method:`self.flush`."""
462 """Callback for :method:`self.flush`."""
464 self.stream.flush()
463 self.stream.flush()
465 self._flushed = True
464 self._flushed = True
466
465
467
466
468 class StdInChannel(ZMQSocketChannel):
467 class StdInChannel(ZMQSocketChannel):
469 """The stdin channel to handle raw_input requests that the kernel makes."""
468 """The stdin channel to handle raw_input requests that the kernel makes."""
470
469
471 msg_queue = None
470 msg_queue = None
472
471
473 def __init__(self, context, session, address):
472 def __init__(self, context, session, address):
474 super(StdInChannel, self).__init__(context, session, address)
473 super(StdInChannel, self).__init__(context, session, address)
475 self.ioloop = ioloop.IOLoop()
474 self.ioloop = ioloop.IOLoop()
476
475
477 def run(self):
476 def run(self):
478 """The thread's main activity. Call start() instead."""
477 """The thread's main activity. Call start() instead."""
479 self.socket = self.context.socket(zmq.DEALER)
478 self.socket = self.context.socket(zmq.DEALER)
480 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
479 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
481 self.socket.connect(self.address)
480 self.socket.connect(self.address)
482 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
481 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
483 self.stream.on_recv(self._handle_recv)
482 self.stream.on_recv(self._handle_recv)
484 self._run_loop()
483 self._run_loop()
485 try:
484 try:
486 self.socket.close()
485 self.socket.close()
487 except:
486 except:
488 pass
487 pass
489
488
490 def stop(self):
489 def stop(self):
491 """Stop the channel's event loop and join its thread."""
490 """Stop the channel's event loop and join its thread."""
492 self.ioloop.stop()
491 self.ioloop.stop()
493 super(StdInChannel, self).stop()
492 super(StdInChannel, self).stop()
494
493
495 def call_handlers(self, msg):
494 def call_handlers(self, msg):
496 """This method is called in the ioloop thread when a message arrives.
495 """This method is called in the ioloop thread when a message arrives.
497
496
498 Subclasses should override this method to handle incoming messages.
497 Subclasses should override this method to handle incoming messages.
499 It is important to remember that this method is called in the thread
498 It is important to remember that this method is called in the thread
500 so that some logic must be done to ensure that the application leve
499 so that some logic must be done to ensure that the application leve
501 handlers are called in the application thread.
500 handlers are called in the application thread.
502 """
501 """
503 raise NotImplementedError('call_handlers must be defined in a subclass.')
502 raise NotImplementedError('call_handlers must be defined in a subclass.')
504
503
505 def input(self, string):
504 def input(self, string):
506 """Send a string of raw input to the kernel."""
505 """Send a string of raw input to the kernel."""
507 content = dict(value=string)
506 content = dict(value=string)
508 msg = self.session.msg('input_reply', content)
507 msg = self.session.msg('input_reply', content)
509 self._queue_send(msg)
508 self._queue_send(msg)
510
509
511
510
512 class HBChannel(ZMQSocketChannel):
511 class HBChannel(ZMQSocketChannel):
513 """The heartbeat channel which monitors the kernel heartbeat.
512 """The heartbeat channel which monitors the kernel heartbeat.
514
513
515 Note that the heartbeat channel is paused by default. As long as you start
514 Note that the heartbeat channel is paused by default. As long as you start
516 this channel, the kernel manager will ensure that it is paused and un-paused
515 this channel, the kernel manager will ensure that it is paused and un-paused
517 as appropriate.
516 as appropriate.
518 """
517 """
519
518
520 time_to_dead = 3.0
519 time_to_dead = 3.0
521 socket = None
520 socket = None
522 poller = None
521 poller = None
523 _running = None
522 _running = None
524 _pause = None
523 _pause = None
525 _beating = None
524 _beating = None
526
525
527 def __init__(self, context, session, address):
526 def __init__(self, context, session, address):
528 super(HBChannel, self).__init__(context, session, address)
527 super(HBChannel, self).__init__(context, session, address)
529 self._running = False
528 self._running = False
530 self._pause =True
529 self._pause =True
531 self.poller = zmq.Poller()
530 self.poller = zmq.Poller()
532
531
533 def _create_socket(self):
532 def _create_socket(self):
534 if self.socket is not None:
533 if self.socket is not None:
535 # close previous socket, before opening a new one
534 # close previous socket, before opening a new one
536 self.poller.unregister(self.socket)
535 self.poller.unregister(self.socket)
537 self.socket.close()
536 self.socket.close()
538 self.socket = self.context.socket(zmq.REQ)
537 self.socket = self.context.socket(zmq.REQ)
539 self.socket.setsockopt(zmq.LINGER, 0)
538 self.socket.setsockopt(zmq.LINGER, 0)
540 self.socket.connect(self.address)
539 self.socket.connect(self.address)
541
540
542 self.poller.register(self.socket, zmq.POLLIN)
541 self.poller.register(self.socket, zmq.POLLIN)
543
542
544 def _poll(self, start_time):
543 def _poll(self, start_time):
545 """poll for heartbeat replies until we reach self.time_to_dead.
544 """poll for heartbeat replies until we reach self.time_to_dead.
546
545
547 Ignores interrupts, and returns the result of poll(), which
546 Ignores interrupts, and returns the result of poll(), which
548 will be an empty list if no messages arrived before the timeout,
547 will be an empty list if no messages arrived before the timeout,
549 or the event tuple if there is a message to receive.
548 or the event tuple if there is a message to receive.
550 """
549 """
551
550
552 until_dead = self.time_to_dead - (time.time() - start_time)
551 until_dead = self.time_to_dead - (time.time() - start_time)
553 # ensure poll at least once
552 # ensure poll at least once
554 until_dead = max(until_dead, 1e-3)
553 until_dead = max(until_dead, 1e-3)
555 events = []
554 events = []
556 while True:
555 while True:
557 try:
556 try:
558 events = self.poller.poll(1000 * until_dead)
557 events = self.poller.poll(1000 * until_dead)
559 except ZMQError as e:
558 except ZMQError as e:
560 if e.errno == errno.EINTR:
559 if e.errno == errno.EINTR:
561 # ignore interrupts during heartbeat
560 # ignore interrupts during heartbeat
562 # this may never actually happen
561 # this may never actually happen
563 until_dead = self.time_to_dead - (time.time() - start_time)
562 until_dead = self.time_to_dead - (time.time() - start_time)
564 until_dead = max(until_dead, 1e-3)
563 until_dead = max(until_dead, 1e-3)
565 pass
564 pass
566 else:
565 else:
567 raise
566 raise
568 except Exception:
567 except Exception:
569 if self._exiting:
568 if self._exiting:
570 break
569 break
571 else:
570 else:
572 raise
571 raise
573 else:
572 else:
574 break
573 break
575 return events
574 return events
576
575
577 def run(self):
576 def run(self):
578 """The thread's main activity. Call start() instead."""
577 """The thread's main activity. Call start() instead."""
579 self._create_socket()
578 self._create_socket()
580 self._running = True
579 self._running = True
581 self._beating = True
580 self._beating = True
582
581
583 while self._running:
582 while self._running:
584 if self._pause:
583 if self._pause:
585 # just sleep, and skip the rest of the loop
584 # just sleep, and skip the rest of the loop
586 time.sleep(self.time_to_dead)
585 time.sleep(self.time_to_dead)
587 continue
586 continue
588
587
589 since_last_heartbeat = 0.0
588 since_last_heartbeat = 0.0
590 # io.rprint('Ping from HB channel') # dbg
589 # io.rprint('Ping from HB channel') # dbg
591 # no need to catch EFSM here, because the previous event was
590 # no need to catch EFSM here, because the previous event was
592 # either a recv or connect, which cannot be followed by EFSM
591 # either a recv or connect, which cannot be followed by EFSM
593 self.socket.send(b'ping')
592 self.socket.send(b'ping')
594 request_time = time.time()
593 request_time = time.time()
595 ready = self._poll(request_time)
594 ready = self._poll(request_time)
596 if ready:
595 if ready:
597 self._beating = True
596 self._beating = True
598 # the poll above guarantees we have something to recv
597 # the poll above guarantees we have something to recv
599 self.socket.recv()
598 self.socket.recv()
600 # sleep the remainder of the cycle
599 # sleep the remainder of the cycle
601 remainder = self.time_to_dead - (time.time() - request_time)
600 remainder = self.time_to_dead - (time.time() - request_time)
602 if remainder > 0:
601 if remainder > 0:
603 time.sleep(remainder)
602 time.sleep(remainder)
604 continue
603 continue
605 else:
604 else:
606 # nothing was received within the time limit, signal heart failure
605 # nothing was received within the time limit, signal heart failure
607 self._beating = False
606 self._beating = False
608 since_last_heartbeat = time.time() - request_time
607 since_last_heartbeat = time.time() - request_time
609 self.call_handlers(since_last_heartbeat)
608 self.call_handlers(since_last_heartbeat)
610 # and close/reopen the socket, because the REQ/REP cycle has been broken
609 # and close/reopen the socket, because the REQ/REP cycle has been broken
611 self._create_socket()
610 self._create_socket()
612 continue
611 continue
613 try:
612 try:
614 self.socket.close()
613 self.socket.close()
615 except:
614 except:
616 pass
615 pass
617
616
618 def pause(self):
617 def pause(self):
619 """Pause the heartbeat."""
618 """Pause the heartbeat."""
620 self._pause = True
619 self._pause = True
621
620
622 def unpause(self):
621 def unpause(self):
623 """Unpause the heartbeat."""
622 """Unpause the heartbeat."""
624 self._pause = False
623 self._pause = False
625
624
626 def is_beating(self):
625 def is_beating(self):
627 """Is the heartbeat running and responsive (and not paused)."""
626 """Is the heartbeat running and responsive (and not paused)."""
628 if self.is_alive() and not self._pause and self._beating:
627 if self.is_alive() and not self._pause and self._beating:
629 return True
628 return True
630 else:
629 else:
631 return False
630 return False
632
631
633 def stop(self):
632 def stop(self):
634 """Stop the channel's event loop and join its thread."""
633 """Stop the channel's event loop and join its thread."""
635 self._running = False
634 self._running = False
636 super(HBChannel, self).stop()
635 super(HBChannel, self).stop()
637
636
638 def call_handlers(self, since_last_heartbeat):
637 def call_handlers(self, since_last_heartbeat):
639 """This method is called in the ioloop thread when a message arrives.
638 """This method is called in the ioloop thread when a message arrives.
640
639
641 Subclasses should override this method to handle incoming messages.
640 Subclasses should override this method to handle incoming messages.
642 It is important to remember that this method is called in the thread
641 It is important to remember that this method is called in the thread
643 so that some logic must be done to ensure that the application level
642 so that some logic must be done to ensure that the application level
644 handlers are called in the application thread.
643 handlers are called in the application thread.
645 """
644 """
646 raise NotImplementedError('call_handlers must be defined in a subclass.')
645 raise NotImplementedError('call_handlers must be defined in a subclass.')
647
646
648
647
649 #-----------------------------------------------------------------------------
648 #-----------------------------------------------------------------------------
650 # Main kernel manager class
649 # Main kernel manager class
651 #-----------------------------------------------------------------------------
650 #-----------------------------------------------------------------------------
652
651
653 class KernelManager(Configurable):
652 class KernelManager(Configurable):
654 """Manages a single kernel on this host along with its channels.
653 """Manages a single kernel on this host along with its channels.
655
654
656 There are four channels associated with each kernel:
655 There are four channels associated with each kernel:
657
656
658 * shell: for request/reply calls to the kernel.
657 * shell: for request/reply calls to the kernel.
659 * iopub: for the kernel to publish results to frontends.
658 * iopub: for the kernel to publish results to frontends.
660 * hb: for monitoring the kernel's heartbeat.
659 * hb: for monitoring the kernel's heartbeat.
661 * stdin: for frontends to reply to raw_input calls in the kernel.
660 * stdin: for frontends to reply to raw_input calls in the kernel.
662
661
663 The usage of the channels that this class manages is optional. It is
662 The usage of the channels that this class manages is optional. It is
664 entirely possible to connect to the kernels directly using ZeroMQ
663 entirely possible to connect to the kernels directly using ZeroMQ
665 sockets. These channels are useful primarily for talking to a kernel
664 sockets. These channels are useful primarily for talking to a kernel
666 whose :class:`KernelManager` is in the same process.
665 whose :class:`KernelManager` is in the same process.
667
666
668 This version manages kernels started using Popen.
667 This version manages kernels started using Popen.
669 """
668 """
670 # The PyZMQ Context to use for communication with the kernel.
669 # The PyZMQ Context to use for communication with the kernel.
671 context = Instance(zmq.Context)
670 context = Instance(zmq.Context)
672 def _context_default(self):
671 def _context_default(self):
673 return zmq.Context.instance()
672 return zmq.Context.instance()
674
673
675 # The Session to use for communication with the kernel.
674 # The Session to use for communication with the kernel.
676 session = Instance(Session)
675 session = Instance(Session)
677 def _session_default(self):
676 def _session_default(self):
678 return Session(config=self.config)
677 return Session(config=self.config)
679
678
680 # The kernel process with which the KernelManager is communicating.
679 # The kernel process with which the KernelManager is communicating.
681 kernel = Instance(Popen)
680 kernel = Instance(Popen)
682
681
683 # The addresses for the communication channels.
682 # The addresses for the communication channels.
684 connection_file = Unicode('')
683 connection_file = Unicode('')
685
684
686 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
685 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
687
686
688 ip = Unicode(LOCALHOST, config=True)
687 ip = Unicode(LOCALHOST, config=True)
689 def _ip_changed(self, name, old, new):
688 def _ip_changed(self, name, old, new):
690 if new == '*':
689 if new == '*':
691 self.ip = '0.0.0.0'
690 self.ip = '0.0.0.0'
692 shell_port = Integer(0)
691 shell_port = Integer(0)
693 iopub_port = Integer(0)
692 iopub_port = Integer(0)
694 stdin_port = Integer(0)
693 stdin_port = Integer(0)
695 hb_port = Integer(0)
694 hb_port = Integer(0)
696
695
697 # The classes to use for the various channels.
696 # The classes to use for the various channels.
698 shell_channel_class = Type(ShellChannel)
697 shell_channel_class = Type(ShellChannel)
699 iopub_channel_class = Type(IOPubChannel)
698 iopub_channel_class = Type(IOPubChannel)
700 stdin_channel_class = Type(StdInChannel)
699 stdin_channel_class = Type(StdInChannel)
701 hb_channel_class = Type(HBChannel)
700 hb_channel_class = Type(HBChannel)
702
701
703 # Protected traits.
702 # Protected traits.
704 _launch_args = Any
703 _launch_args = Any
705 _shell_channel = Any
704 _shell_channel = Any
706 _iopub_channel = Any
705 _iopub_channel = Any
707 _stdin_channel = Any
706 _stdin_channel = Any
708 _hb_channel = Any
707 _hb_channel = Any
709 _connection_file_written=Bool(False)
708 _connection_file_written=Bool(False)
710
709
711 def __del__(self):
710 def __del__(self):
712 self.cleanup_connection_file()
711 self.cleanup_connection_file()
713
712
714 #--------------------------------------------------------------------------
713 #--------------------------------------------------------------------------
715 # Channel management methods:
714 # Channel management methods:
716 #--------------------------------------------------------------------------
715 #--------------------------------------------------------------------------
717
716
718 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
717 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
719 """Starts the channels for this kernel.
718 """Starts the channels for this kernel.
720
719
721 This will create the channels if they do not exist and then start
720 This will create the channels if they do not exist and then start
722 them (their activity runs in a thread). If port numbers of 0 are
721 them (their activity runs in a thread). If port numbers of 0 are
723 being used (random ports) then you must first call
722 being used (random ports) then you must first call
724 :method:`start_kernel`. If the channels have been stopped and you
723 :method:`start_kernel`. If the channels have been stopped and you
725 call this, :class:`RuntimeError` will be raised.
724 call this, :class:`RuntimeError` will be raised.
726 """
725 """
727 if shell:
726 if shell:
728 self.shell_channel.start()
727 self.shell_channel.start()
729 if iopub:
728 if iopub:
730 self.iopub_channel.start()
729 self.iopub_channel.start()
731 if stdin:
730 if stdin:
732 self.stdin_channel.start()
731 self.stdin_channel.start()
733 self.shell_channel.allow_stdin = True
732 self.shell_channel.allow_stdin = True
734 else:
733 else:
735 self.shell_channel.allow_stdin = False
734 self.shell_channel.allow_stdin = False
736 if hb:
735 if hb:
737 self.hb_channel.start()
736 self.hb_channel.start()
738
737
739 def stop_channels(self):
738 def stop_channels(self):
740 """Stops all the running channels for this kernel.
739 """Stops all the running channels for this kernel.
741
740
742 This stops their event loops and joins their threads.
741 This stops their event loops and joins their threads.
743 """
742 """
744 if self.shell_channel.is_alive():
743 if self.shell_channel.is_alive():
745 self.shell_channel.stop()
744 self.shell_channel.stop()
746 if self.iopub_channel.is_alive():
745 if self.iopub_channel.is_alive():
747 self.iopub_channel.stop()
746 self.iopub_channel.stop()
748 if self.stdin_channel.is_alive():
747 if self.stdin_channel.is_alive():
749 self.stdin_channel.stop()
748 self.stdin_channel.stop()
750 if self.hb_channel.is_alive():
749 if self.hb_channel.is_alive():
751 self.hb_channel.stop()
750 self.hb_channel.stop()
752
751
753 @property
752 @property
754 def channels_running(self):
753 def channels_running(self):
755 """Are any of the channels created and running?"""
754 """Are any of the channels created and running?"""
756 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
755 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
757 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
756 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
758
757
759 def _make_url(self, port):
758 def _make_url(self, port):
760 """Make a zmq url with a port.
759 """Make a zmq url with a port.
761
760
762 There are two cases that this handles:
761 There are two cases that this handles:
763
762
764 * tcp: tcp://ip:port
763 * tcp: tcp://ip:port
765 * ipc: ipc://ip-port
764 * ipc: ipc://ip-port
766 """
765 """
767 if self.transport == 'tcp':
766 if self.transport == 'tcp':
768 return "tcp://%s:%i" % (self.ip, port)
767 return "tcp://%s:%i" % (self.ip, port)
769 else:
768 else:
770 return "%s://%s-%s" % (self.transport, self.ip, port)
769 return "%s://%s-%s" % (self.transport, self.ip, port)
771
770
772 @property
771 @property
773 def shell_channel(self):
772 def shell_channel(self):
774 """Get the shell channel object for this kernel."""
773 """Get the shell channel object for this kernel."""
775 if self._shell_channel is None:
774 if self._shell_channel is None:
776 self._shell_channel = self.shell_channel_class(
775 self._shell_channel = self.shell_channel_class(
777 self.context, self.session, self._make_url(self.shell_port)
776 self.context, self.session, self._make_url(self.shell_port)
778 )
777 )
779 return self._shell_channel
778 return self._shell_channel
780
779
781 @property
780 @property
782 def iopub_channel(self):
781 def iopub_channel(self):
783 """Get the iopub channel object for this kernel."""
782 """Get the iopub channel object for this kernel."""
784 if self._iopub_channel is None:
783 if self._iopub_channel is None:
785 self._iopub_channel = self.iopub_channel_class(
784 self._iopub_channel = self.iopub_channel_class(
786 self.context, self.session, self._make_url(self.iopub_port)
785 self.context, self.session, self._make_url(self.iopub_port)
787 )
786 )
788 return self._iopub_channel
787 return self._iopub_channel
789
788
790 @property
789 @property
791 def stdin_channel(self):
790 def stdin_channel(self):
792 """Get the stdin channel object for this kernel."""
791 """Get the stdin channel object for this kernel."""
793 if self._stdin_channel is None:
792 if self._stdin_channel is None:
794 self._stdin_channel = self.stdin_channel_class(
793 self._stdin_channel = self.stdin_channel_class(
795 self.context, self.session, self._make_url(self.stdin_port)
794 self.context, self.session, self._make_url(self.stdin_port)
796 )
795 )
797 return self._stdin_channel
796 return self._stdin_channel
798
797
799 @property
798 @property
800 def hb_channel(self):
799 def hb_channel(self):
801 """Get the hb channel object for this kernel."""
800 """Get the hb channel object for this kernel."""
802 if self._hb_channel is None:
801 if self._hb_channel is None:
803 self._hb_channel = self.hb_channel_class(
802 self._hb_channel = self.hb_channel_class(
804 self.context, self.session, self._make_url(self.hb_port)
803 self.context, self.session, self._make_url(self.hb_port)
805 )
804 )
806 return self._hb_channel
805 return self._hb_channel
807
806
808 #--------------------------------------------------------------------------
807 #--------------------------------------------------------------------------
809 # Connection and ipc file management.
808 # Connection and ipc file management.
810 #--------------------------------------------------------------------------
809 #--------------------------------------------------------------------------
811
810
812 def cleanup_connection_file(self):
811 def cleanup_connection_file(self):
813 """Cleanup connection file *if we wrote it*
812 """Cleanup connection file *if we wrote it*
814
813
815 Will not raise if the connection file was already removed somehow.
814 Will not raise if the connection file was already removed somehow.
816 """
815 """
817 if self._connection_file_written:
816 if self._connection_file_written:
818 # cleanup connection files on full shutdown of kernel we started
817 # cleanup connection files on full shutdown of kernel we started
819 self._connection_file_written = False
818 self._connection_file_written = False
820 try:
819 try:
821 os.remove(self.connection_file)
820 os.remove(self.connection_file)
822 except (IOError, OSError):
821 except (IOError, OSError):
823 pass
822 pass
824
823
825 self.cleanup_ipc_files()
824 self.cleanup_ipc_files()
826
825
827 def cleanup_ipc_files(self):
826 def cleanup_ipc_files(self):
828 """Cleanup ipc files if we wrote them."""
827 """Cleanup ipc files if we wrote them."""
829 if self.transport != 'ipc':
828 if self.transport != 'ipc':
830 return
829 return
831 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
830 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
832 ipcfile = "%s-%i" % (self.ip, port)
831 ipcfile = "%s-%i" % (self.ip, port)
833 try:
832 try:
834 os.remove(ipcfile)
833 os.remove(ipcfile)
835 except (IOError, OSError):
834 except (IOError, OSError):
836 pass
835 pass
837
836
838 def load_connection_file(self):
837 def load_connection_file(self):
839 """Load connection info from JSON dict in self.connection_file."""
838 """Load connection info from JSON dict in self.connection_file."""
840 with open(self.connection_file) as f:
839 with open(self.connection_file) as f:
841 cfg = json.loads(f.read())
840 cfg = json.loads(f.read())
842
841
843 from pprint import pprint
842 from pprint import pprint
844 pprint(cfg)
843 pprint(cfg)
845 self.transport = cfg.get('transport', 'tcp')
844 self.transport = cfg.get('transport', 'tcp')
846 self.ip = cfg['ip']
845 self.ip = cfg['ip']
847 self.shell_port = cfg['shell_port']
846 self.shell_port = cfg['shell_port']
848 self.stdin_port = cfg['stdin_port']
847 self.stdin_port = cfg['stdin_port']
849 self.iopub_port = cfg['iopub_port']
848 self.iopub_port = cfg['iopub_port']
850 self.hb_port = cfg['hb_port']
849 self.hb_port = cfg['hb_port']
851 self.session.key = str_to_bytes(cfg['key'])
850 self.session.key = str_to_bytes(cfg['key'])
852
851
853 def write_connection_file(self):
852 def write_connection_file(self):
854 """Write connection info to JSON dict in self.connection_file."""
853 """Write connection info to JSON dict in self.connection_file."""
855 if self._connection_file_written:
854 if self._connection_file_written:
856 return
855 return
857 self.connection_file,cfg = write_connection_file(self.connection_file,
856 self.connection_file,cfg = write_connection_file(self.connection_file,
858 transport=self.transport, ip=self.ip, key=self.session.key,
857 transport=self.transport, ip=self.ip, key=self.session.key,
859 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
858 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
860 shell_port=self.shell_port, hb_port=self.hb_port)
859 shell_port=self.shell_port, hb_port=self.hb_port)
861 # write_connection_file also sets default ports:
860 # write_connection_file also sets default ports:
862 self.shell_port = cfg['shell_port']
861 self.shell_port = cfg['shell_port']
863 self.stdin_port = cfg['stdin_port']
862 self.stdin_port = cfg['stdin_port']
864 self.iopub_port = cfg['iopub_port']
863 self.iopub_port = cfg['iopub_port']
865 self.hb_port = cfg['hb_port']
864 self.hb_port = cfg['hb_port']
866
865
867 self._connection_file_written = True
866 self._connection_file_written = True
868
867
869 #--------------------------------------------------------------------------
868 #--------------------------------------------------------------------------
870 # Kernel management.
869 # Kernel management.
871 #--------------------------------------------------------------------------
870 #--------------------------------------------------------------------------
872
871
873 def start_kernel(self, **kw):
872 def start_kernel(self, **kw):
874 """Starts a kernel on this host in a separate process.
873 """Starts a kernel on this host in a separate process.
875
874
876 If random ports (port=0) are being used, this method must be called
875 If random ports (port=0) are being used, this method must be called
877 before the channels are created.
876 before the channels are created.
878
877
879 Parameters:
878 Parameters:
880 -----------
879 -----------
881 launcher : callable, optional (default None)
880 launcher : callable, optional (default None)
882 A custom function for launching the kernel process (generally a
881 A custom function for launching the kernel process (generally a
883 wrapper around ``entry_point.base_launch_kernel``). In most cases,
882 wrapper around ``entry_point.base_launch_kernel``). In most cases,
884 it should not be necessary to use this parameter.
883 it should not be necessary to use this parameter.
885
884
886 **kw : optional
885 **kw : optional
887 keyword arguments that are passed down into the launcher
886 keyword arguments that are passed down into the launcher
888 callable.
887 callable.
889 """
888 """
890 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
889 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
891 raise RuntimeError("Can only launch a kernel on a local interface. "
890 raise RuntimeError("Can only launch a kernel on a local interface. "
892 "Make sure that the '*_address' attributes are "
891 "Make sure that the '*_address' attributes are "
893 "configured properly. "
892 "configured properly. "
894 "Currently valid addresses are: %s"%LOCAL_IPS
893 "Currently valid addresses are: %s"%LOCAL_IPS
895 )
894 )
896
895
897 # write connection file / get default ports
896 # write connection file / get default ports
898 self.write_connection_file()
897 self.write_connection_file()
899
898
900 self._launch_args = kw.copy()
899 self._launch_args = kw.copy()
901 launch_kernel = kw.pop('launcher', None)
900 launch_kernel = kw.pop('launcher', None)
902 if launch_kernel is None:
901 if launch_kernel is None:
903 from ipkernel import launch_kernel
902 from ipkernel import launch_kernel
904 self.kernel = launch_kernel(fname=self.connection_file, **kw)
903 self.kernel = launch_kernel(fname=self.connection_file, **kw)
905
904
906 def shutdown_kernel(self, now=False, restart=False):
905 def shutdown_kernel(self, now=False, restart=False):
907 """Attempts to the stop the kernel process cleanly.
906 """Attempts to the stop the kernel process cleanly.
908
907
909 This attempts to shutdown the kernels cleanly by:
908 This attempts to shutdown the kernels cleanly by:
910
909
911 1. Sending it a shutdown message over the shell channel.
910 1. Sending it a shutdown message over the shell channel.
912 2. If that fails, the kernel is shutdown forcibly by sending it
911 2. If that fails, the kernel is shutdown forcibly by sending it
913 a signal.
912 a signal.
914
913
915 Parameters:
914 Parameters:
916 -----------
915 -----------
917 now : bool
916 now : bool
918 Should the kernel be forcible killed *now*. This skips the
917 Should the kernel be forcible killed *now*. This skips the
919 first, nice shutdown attempt.
918 first, nice shutdown attempt.
920 restart: bool
919 restart: bool
921 Will this kernel be restarted after it is shutdown. When this
920 Will this kernel be restarted after it is shutdown. When this
922 is True, connection files will not be cleaned up.
921 is True, connection files will not be cleaned up.
923 """
922 """
924 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
923 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
925 if sys.platform == 'win32':
924 if sys.platform == 'win32':
926 self._kill_kernel()
925 self._kill_kernel()
927 return
926 return
928
927
929 # Pause the heart beat channel if it exists.
928 # Pause the heart beat channel if it exists.
930 if self._hb_channel is not None:
929 if self._hb_channel is not None:
931 self._hb_channel.pause()
930 self._hb_channel.pause()
932
931
933 if now:
932 if now:
934 if self.has_kernel:
933 if self.has_kernel:
935 self._kill_kernel()
934 self._kill_kernel()
936 else:
935 else:
937 # Don't send any additional kernel kill messages immediately, to give
936 # Don't send any additional kernel kill messages immediately, to give
938 # the kernel a chance to properly execute shutdown actions. Wait for at
937 # the kernel a chance to properly execute shutdown actions. Wait for at
939 # most 1s, checking every 0.1s.
938 # most 1s, checking every 0.1s.
940 self.shell_channel.shutdown(restart=restart)
939 self.shell_channel.shutdown(restart=restart)
941 for i in range(10):
940 for i in range(10):
942 if self.is_alive:
941 if self.is_alive:
943 time.sleep(0.1)
942 time.sleep(0.1)
944 else:
943 else:
945 break
944 break
946 else:
945 else:
947 # OK, we've waited long enough.
946 # OK, we've waited long enough.
948 if self.has_kernel:
947 if self.has_kernel:
949 self._kill_kernel()
948 self._kill_kernel()
950
949
951 if not restart:
950 if not restart:
952 self.cleanup_connection_file()
951 self.cleanup_connection_file()
953 else:
952 else:
954 self.cleanup_ipc_files()
953 self.cleanup_ipc_files()
955
954
956 def restart_kernel(self, now=False, **kw):
955 def restart_kernel(self, now=False, **kw):
957 """Restarts a kernel with the arguments that were used to launch it.
956 """Restarts a kernel with the arguments that were used to launch it.
958
957
959 If the old kernel was launched with random ports, the same ports will be
958 If the old kernel was launched with random ports, the same ports will be
960 used for the new kernel. The same connection file is used again.
959 used for the new kernel. The same connection file is used again.
961
960
962 Parameters
961 Parameters
963 ----------
962 ----------
964 now : bool, optional
963 now : bool, optional
965 If True, the kernel is forcefully restarted *immediately*, without
964 If True, the kernel is forcefully restarted *immediately*, without
966 having a chance to do any cleanup action. Otherwise the kernel is
965 having a chance to do any cleanup action. Otherwise the kernel is
967 given 1s to clean up before a forceful restart is issued.
966 given 1s to clean up before a forceful restart is issued.
968
967
969 In all cases the kernel is restarted, the only difference is whether
968 In all cases the kernel is restarted, the only difference is whether
970 it is given a chance to perform a clean shutdown or not.
969 it is given a chance to perform a clean shutdown or not.
971
970
972 **kw : optional
971 **kw : optional
973 Any options specified here will overwrite those used to launch the
972 Any options specified here will overwrite those used to launch the
974 kernel.
973 kernel.
975 """
974 """
976 if self._launch_args is None:
975 if self._launch_args is None:
977 raise RuntimeError("Cannot restart the kernel. "
976 raise RuntimeError("Cannot restart the kernel. "
978 "No previous call to 'start_kernel'.")
977 "No previous call to 'start_kernel'.")
979 else:
978 else:
980 # Stop currently running kernel.
979 # Stop currently running kernel.
981 self.shutdown_kernel(now=now, restart=True)
980 self.shutdown_kernel(now=now, restart=True)
982
981
983 # Start new kernel.
982 # Start new kernel.
984 self._launch_args.update(kw)
983 self._launch_args.update(kw)
985 self.start_kernel(**self._launch_args)
984 self.start_kernel(**self._launch_args)
986
985
987 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
986 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
988 # unless there is some delay here.
987 # unless there is some delay here.
989 if sys.platform == 'win32':
988 if sys.platform == 'win32':
990 time.sleep(0.2)
989 time.sleep(0.2)
991
990
992 @property
991 @property
993 def has_kernel(self):
992 def has_kernel(self):
994 """Has a kernel been started that we are managing."""
993 """Has a kernel been started that we are managing."""
995 return self.kernel is not None
994 return self.kernel is not None
996
995
997 def _kill_kernel(self):
996 def _kill_kernel(self):
998 """Kill the running kernel.
997 """Kill the running kernel.
999
998
1000 This is a private method, callers should use shutdown_kernel(now=True).
999 This is a private method, callers should use shutdown_kernel(now=True).
1001 """
1000 """
1002 if self.has_kernel:
1001 if self.has_kernel:
1003 # Pause the heart beat channel if it exists.
1002 # Pause the heart beat channel if it exists.
1004 if self._hb_channel is not None:
1003 if self._hb_channel is not None:
1005 self._hb_channel.pause()
1004 self._hb_channel.pause()
1006
1005
1007 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1006 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1008 # TerminateProcess() on Win32).
1007 # TerminateProcess() on Win32).
1009 try:
1008 try:
1010 self.kernel.kill()
1009 self.kernel.kill()
1011 except OSError as e:
1010 except OSError as e:
1012 # In Windows, we will get an Access Denied error if the process
1011 # In Windows, we will get an Access Denied error if the process
1013 # has already terminated. Ignore it.
1012 # has already terminated. Ignore it.
1014 if sys.platform == 'win32':
1013 if sys.platform == 'win32':
1015 if e.winerror != 5:
1014 if e.winerror != 5:
1016 raise
1015 raise
1017 # On Unix, we may get an ESRCH error if the process has already
1016 # On Unix, we may get an ESRCH error if the process has already
1018 # terminated. Ignore it.
1017 # terminated. Ignore it.
1019 else:
1018 else:
1020 from errno import ESRCH
1019 from errno import ESRCH
1021 if e.errno != ESRCH:
1020 if e.errno != ESRCH:
1022 raise
1021 raise
1023
1022
1024 # Block until the kernel terminates.
1023 # Block until the kernel terminates.
1025 self.kernel.wait()
1024 self.kernel.wait()
1026 self.kernel = None
1025 self.kernel = None
1027 else:
1026 else:
1028 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1027 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1029
1028
1030 def interrupt_kernel(self):
1029 def interrupt_kernel(self):
1031 """Interrupts the kernel by sending it a signal.
1030 """Interrupts the kernel by sending it a signal.
1032
1031
1033 Unlike ``signal_kernel``, this operation is well supported on all
1032 Unlike ``signal_kernel``, this operation is well supported on all
1034 platforms.
1033 platforms.
1035 """
1034 """
1036 if self.has_kernel:
1035 if self.has_kernel:
1037 if sys.platform == 'win32':
1036 if sys.platform == 'win32':
1038 from parentpoller import ParentPollerWindows as Poller
1037 from parentpoller import ParentPollerWindows as Poller
1039 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1038 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1040 else:
1039 else:
1041 self.kernel.send_signal(signal.SIGINT)
1040 self.kernel.send_signal(signal.SIGINT)
1042 else:
1041 else:
1043 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1042 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1044
1043
1045 def signal_kernel(self, signum):
1044 def signal_kernel(self, signum):
1046 """Sends a signal to the kernel.
1045 """Sends a signal to the kernel.
1047
1046
1048 Note that since only SIGTERM is supported on Windows, this function is
1047 Note that since only SIGTERM is supported on Windows, this function is
1049 only useful on Unix systems.
1048 only useful on Unix systems.
1050 """
1049 """
1051 if self.has_kernel:
1050 if self.has_kernel:
1052 self.kernel.send_signal(signum)
1051 self.kernel.send_signal(signum)
1053 else:
1052 else:
1054 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1053 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1055
1054
1056 @property
1055 @property
1057 def is_alive(self):
1056 def is_alive(self):
1058 """Is the kernel process still running?"""
1057 """Is the kernel process still running?"""
1059 if self.has_kernel:
1058 if self.has_kernel:
1060 if self.kernel.poll() is None:
1059 if self.kernel.poll() is None:
1061 return True
1060 return True
1062 else:
1061 else:
1063 return False
1062 return False
1064 elif self._hb_channel is not None:
1063 elif self._hb_channel is not None:
1065 # We didn't start the kernel with this KernelManager so we
1064 # We didn't start the kernel with this KernelManager so we
1066 # use the heartbeat.
1065 # use the heartbeat.
1067 return self._hb_channel.is_beating()
1066 return self._hb_channel.is_beating()
1068 else:
1067 else:
1069 # no heartbeat and not local, we can't tell if it's running,
1068 # no heartbeat and not local, we can't tell if it's running,
1070 # so naively return True
1069 # so naively return True
1071 return True
1070 return True
1072
1071
1073
1072
1074 #-----------------------------------------------------------------------------
1073 #-----------------------------------------------------------------------------
1075 # ABC Registration
1074 # ABC Registration
1076 #-----------------------------------------------------------------------------
1075 #-----------------------------------------------------------------------------
1077
1076
1078 ShellChannelABC.register(ShellChannel)
1077 ShellChannelABC.register(ShellChannel)
1079 IOPubChannelABC.register(IOPubChannel)
1078 IOPubChannelABC.register(IOPubChannel)
1080 HBChannelABC.register(HBChannel)
1079 HBChannelABC.register(HBChannel)
1081 StdInChannelABC.register(StdInChannel)
1080 StdInChannelABC.register(StdInChannel)
1082 KernelManagerABC.register(KernelManager)
1081 KernelManagerABC.register(KernelManager)
1083
1082
@@ -1,36 +1,44 b''
1 """Tests for the notebook kernel and session manager."""
1 """Tests for the notebook kernel and session manager."""
2
2
3 import time
3 from unittest import TestCase
4 from unittest import TestCase
4
5
5 from IPython.config.loader import Config
6 from IPython.config.loader import Config
6 from IPython.zmq.kernelmanager import KernelManager
7 from IPython.zmq.kernelmanager import KernelManager
7
8
8 class TestKernelManager(TestCase):
9 class TestKernelManager(TestCase):
9
10
10 def _get_tcp_km(self):
11 def _get_tcp_km(self):
11 return KernelManager()
12 return KernelManager()
12
13
13 def _get_ipc_km(self):
14 def _get_ipc_km(self):
14 c = Config()
15 c = Config()
15 c.KernelManager.transport = 'ipc'
16 c.KernelManager.transport = 'ipc'
16 c.KernelManager.ip = 'test'
17 c.KernelManager.ip = 'test'
17 km = KernelManager(config=c)
18 km = KernelManager(config=c)
18 return km
19 return km
19
20
20 def _run_lifecycle(self, km):
21 def _run_lifecycle(self, km):
21 km.start_kernel()
22 km.start_kernel()
22 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
23 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
23 # km.shell_channel.start()
24 km.restart_kernel()
24 km.restart_kernel()
25 # We need a delay here to give the restarting kernel a chance to
26 # restart. Otherwise, the interrupt will kill it, causing the test
27 # suite to hang. The reason it *hangs* is that the shutdown
28 # message for the restart sometimes hasn't been sent to the kernel.
29 # Because linger is oo on the shell channel, the context can't
30 # close until the message is sent to the kernel, which is not dead.
31 time.sleep()
25 km.interrupt_kernel()
32 km.interrupt_kernel()
26 self.assertTrue(isinstance(km, KernelManager))
33 self.assertTrue(isinstance(km, KernelManager))
27 km.shutdown_kernel()
34 km.shutdown_kernel()
28 km.shell_channel.stop()
35 km.shell_channel.stop()
29
36
30 def test_km_tcp(self):
37 def test_tcp_lifecycle(self):
31 km = self._get_tcp_km()
38 km = self._get_tcp_km()
32 self._run_lifecycle(km)
39 self._run_lifecycle(km)
33
40
34 def test_km_ipc(self):
41 def testipc_lifecycle(self):
35 km = self._get_ipc_km()
42 km = self._get_ipc_km()
36 self._run_lifecycle(km)
43 self._run_lifecycle(km)
44
General Comments 0
You need to be logged in to leave comments. Login now