##// END OF EJS Templates
use pyzmq tools where appropriate...
MinRK -
Show More
@@ -1,333 +1,332 b''
1 1 """A tornado based IPython notebook server.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib
20 20 import errno
21 21 import logging
22 22 import os
23 23 import signal
24 24 import socket
25 25 import sys
26 26 import threading
27 27 import webbrowser
28 28
29 29 # Third party
30 30 import zmq
31 31
32 32 # Install the pyzmq ioloop. This has to be done before anything else from
33 33 # tornado is imported.
34 34 from zmq.eventloop import ioloop
35 import tornado.ioloop
36 tornado.ioloop.IOLoop = ioloop.IOLoop
35 ioloop.install()
37 36
38 37 from tornado import httpserver
39 38 from tornado import web
40 39
41 40 # Our own libraries
42 41 from .kernelmanager import MappingKernelManager
43 42 from .handlers import (LoginHandler, LogoutHandler,
44 43 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
45 44 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
46 45 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
47 46 )
48 47 from .notebookmanager import NotebookManager
49 48
50 49 from IPython.config.application import catch_config_error
51 50 from IPython.core.application import BaseIPythonApplication
52 51 from IPython.core.profiledir import ProfileDir
53 52 from IPython.zmq.session import Session, default_secure
54 53 from IPython.zmq.zmqshell import ZMQInteractiveShell
55 54 from IPython.zmq.ipkernel import (
56 55 flags as ipkernel_flags,
57 56 aliases as ipkernel_aliases,
58 57 IPKernelApp
59 58 )
60 59 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
61 60
62 61 #-----------------------------------------------------------------------------
63 62 # Module globals
64 63 #-----------------------------------------------------------------------------
65 64
66 65 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
67 66 _kernel_action_regex = r"(?P<action>restart|interrupt)"
68 67 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
69 68
70 69 LOCALHOST = '127.0.0.1'
71 70
72 71 _examples = """
73 72 ipython notebook # start the notebook
74 73 ipython notebook --profile=sympy # use the sympy profile
75 74 ipython notebook --pylab=inline # pylab in inline plotting mode
76 75 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
77 76 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
78 77 """
79 78
80 79 #-----------------------------------------------------------------------------
81 80 # The Tornado web application
82 81 #-----------------------------------------------------------------------------
83 82
84 83 class NotebookWebApplication(web.Application):
85 84
86 85 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
87 86 handlers = [
88 87 (r"/", ProjectDashboardHandler),
89 88 (r"/login", LoginHandler),
90 89 (r"/logout", LogoutHandler),
91 90 (r"/new", NewHandler),
92 91 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
93 92 (r"/kernels", MainKernelHandler),
94 93 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
95 94 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
96 95 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
97 96 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
98 97 (r"/notebooks", NotebookRootHandler),
99 98 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
100 99 (r"/rstservice/render", RSTHandler)
101 100 ]
102 101 settings = dict(
103 102 template_path=os.path.join(os.path.dirname(__file__), "templates"),
104 103 static_path=os.path.join(os.path.dirname(__file__), "static"),
105 104 cookie_secret=os.urandom(1024),
106 105 login_url="/login",
107 106 )
108 107 web.Application.__init__(self, handlers, **settings)
109 108
110 109 self.kernel_manager = kernel_manager
111 110 self.log = log
112 111 self.notebook_manager = notebook_manager
113 112 self.ipython_app = ipython_app
114 113 self.read_only = self.ipython_app.read_only
115 114
116 115
117 116 #-----------------------------------------------------------------------------
118 117 # Aliases and Flags
119 118 #-----------------------------------------------------------------------------
120 119
121 120 flags = dict(ipkernel_flags)
122 121 flags['no-browser']=(
123 122 {'NotebookApp' : {'open_browser' : False}},
124 123 "Don't open the notebook in a browser after startup."
125 124 )
126 125 flags['read-only'] = (
127 126 {'NotebookApp' : {'read_only' : True}},
128 127 """Allow read-only access to notebooks.
129 128
130 129 When using a password to protect the notebook server, this flag
131 130 allows unauthenticated clients to view the notebook list, and
132 131 individual notebooks, but not edit them, start kernels, or run
133 132 code.
134 133
135 134 If no password is set, the server will be entirely read-only.
136 135 """
137 136 )
138 137
139 138 # the flags that are specific to the frontend
140 139 # these must be scrubbed before being passed to the kernel,
141 140 # or it will raise an error on unrecognized flags
142 141 notebook_flags = ['no-browser', 'read-only']
143 142
144 143 aliases = dict(ipkernel_aliases)
145 144
146 145 aliases.update({
147 146 'ip': 'NotebookApp.ip',
148 147 'port': 'NotebookApp.port',
149 148 'keyfile': 'NotebookApp.keyfile',
150 149 'certfile': 'NotebookApp.certfile',
151 150 'notebook-dir': 'NotebookManager.notebook_dir',
152 151 })
153 152
154 153 # remove ipkernel flags that are singletons, and don't make sense in
155 154 # multi-kernel evironment:
156 155 aliases.pop('f', None)
157 156
158 157 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
159 158 u'notebook-dir']
160 159
161 160 #-----------------------------------------------------------------------------
162 161 # NotebookApp
163 162 #-----------------------------------------------------------------------------
164 163
165 164 class NotebookApp(BaseIPythonApplication):
166 165
167 166 name = 'ipython-notebook'
168 167 default_config_file_name='ipython_notebook_config.py'
169 168
170 169 description = """
171 170 The IPython HTML Notebook.
172 171
173 172 This launches a Tornado based HTML Notebook Server that serves up an
174 173 HTML5/Javascript Notebook client.
175 174 """
176 175 examples = _examples
177 176
178 177 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
179 178 MappingKernelManager, NotebookManager]
180 179 flags = Dict(flags)
181 180 aliases = Dict(aliases)
182 181
183 182 kernel_argv = List(Unicode)
184 183
185 184 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
186 185 default_value=logging.INFO,
187 186 config=True,
188 187 help="Set the log level by value or name.")
189 188
190 189 # Network related information.
191 190
192 191 ip = Unicode(LOCALHOST, config=True,
193 192 help="The IP address the notebook server will listen on."
194 193 )
195 194
196 195 def _ip_changed(self, name, old, new):
197 196 if new == u'*': self.ip = u''
198 197
199 198 port = Integer(8888, config=True,
200 199 help="The port the notebook server will listen on."
201 200 )
202 201
203 202 certfile = Unicode(u'', config=True,
204 203 help="""The full path to an SSL/TLS certificate file."""
205 204 )
206 205
207 206 keyfile = Unicode(u'', config=True,
208 207 help="""The full path to a private key file for usage with SSL/TLS."""
209 208 )
210 209
211 210 password = Unicode(u'', config=True,
212 211 help="""Hashed password to use for web authentication.
213 212
214 213 To generate, type in a python/IPython shell:
215 214
216 215 from IPython.lib import passwd; passwd()
217 216
218 217 The string should be of the form type:salt:hashed-password.
219 218 """
220 219 )
221 220
222 221 open_browser = Bool(True, config=True,
223 222 help="Whether to open in a browser after starting.")
224 223
225 224 read_only = Bool(False, config=True,
226 225 help="Whether to prevent editing/execution of notebooks."
227 226 )
228 227
229 228 def parse_command_line(self, argv=None):
230 229 super(NotebookApp, self).parse_command_line(argv)
231 230 if argv is None:
232 231 argv = sys.argv[1:]
233 232
234 233 self.kernel_argv = list(argv) # copy
235 234 # Kernel should inherit default config file from frontend
236 235 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
237 236 # Scrub frontend-specific flags
238 237 for a in argv:
239 238 if a.startswith('-') and a.lstrip('-') in notebook_flags:
240 239 self.kernel_argv.remove(a)
241 240 swallow_next = False
242 241 for a in argv:
243 242 if swallow_next:
244 243 self.kernel_argv.remove(a)
245 244 swallow_next = False
246 245 continue
247 246 if a.startswith('-'):
248 247 split = a.lstrip('-').split('=')
249 248 alias = split[0]
250 249 if alias in notebook_aliases:
251 250 self.kernel_argv.remove(a)
252 251 if len(split) == 1:
253 252 # alias passed with arg via space
254 253 swallow_next = True
255 254
256 255 def init_configurables(self):
257 256 # Don't let Qt or ZMQ swallow KeyboardInterupts.
258 257 signal.signal(signal.SIGINT, signal.SIG_DFL)
259 258
260 259 # force Session default to be secure
261 260 default_secure(self.config)
262 261 # Create a KernelManager and start a kernel.
263 262 self.kernel_manager = MappingKernelManager(
264 263 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
265 264 connection_dir = self.profile_dir.security_dir,
266 265 )
267 266 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
268 267 self.notebook_manager.list_notebooks()
269 268
270 269 def init_logging(self):
271 270 super(NotebookApp, self).init_logging()
272 271 # This prevents double log messages because tornado use a root logger that
273 272 # self.log is a child of. The logging module dipatches log messages to a log
274 273 # and all of its ancenstors until propagate is set to False.
275 274 self.log.propagate = False
276 275
277 276 @catch_config_error
278 277 def initialize(self, argv=None):
279 278 super(NotebookApp, self).initialize(argv)
280 279 self.init_configurables()
281 280 self.web_app = NotebookWebApplication(
282 281 self, self.kernel_manager, self.notebook_manager, self.log
283 282 )
284 283 if self.certfile:
285 284 ssl_options = dict(certfile=self.certfile)
286 285 if self.keyfile:
287 286 ssl_options['keyfile'] = self.keyfile
288 287 else:
289 288 ssl_options = None
290 289 self.web_app.password = self.password
291 290 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
292 291 if ssl_options is None and not self.ip:
293 292 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
294 293 'but not using any encryption or authentication. This is highly '
295 294 'insecure and not recommended.')
296 295
297 296 # Try random ports centered around the default.
298 297 from random import randint
299 298 n = 50 # Max number of attempts, keep reasonably large.
300 299 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
301 300 try:
302 301 self.http_server.listen(port, self.ip)
303 302 except socket.error, e:
304 303 if e.errno != errno.EADDRINUSE:
305 304 raise
306 305 self.log.info('The port %i is already in use, trying another random port.' % port)
307 306 else:
308 307 self.port = port
309 308 break
310 309
311 310 def start(self):
312 311 ip = self.ip if self.ip else '[all ip addresses on your system]'
313 312 proto = 'https' if self.certfile else 'http'
314 313 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
315 314 ip,
316 315 self.port))
317 316 if self.open_browser:
318 317 ip = self.ip or '127.0.0.1'
319 318 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
320 319 new=2)
321 320 threading.Thread(target=b).start()
322 321
323 322 ioloop.IOLoop.instance().start()
324 323
325 324 #-----------------------------------------------------------------------------
326 325 # Main entry point
327 326 #-----------------------------------------------------------------------------
328 327
329 328 def launch_new_instance():
330 329 app = NotebookApp()
331 330 app.initialize()
332 331 app.start()
333 332
@@ -1,1046 +1,947 b''
1 1 """Base classes to manage the interaction with a running kernel.
2 2
3 3 TODO
4 4 * Create logger to handle debugging and console messages.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2010 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 # Standard library imports.
19 19 import errno
20 20 import json
21 from Queue import Queue, Empty
22 21 from subprocess import Popen
23 22 import os
24 23 import signal
25 24 import sys
26 25 from threading import Thread
27 26 import time
28 27
29 28 # System library imports.
30 29 import zmq
31 from zmq import POLLIN, POLLOUT, POLLERR
32 from zmq.eventloop import ioloop
30 from zmq.eventloop import ioloop, zmqstream
33 31
34 32 # Local imports.
35 33 from IPython.config.loader import Config
36 34 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
37 35 from IPython.utils.traitlets import (
38 36 HasTraits, Any, Instance, Type, Unicode, Integer, Bool
39 37 )
40 38 from IPython.utils.py3compat import str_to_bytes
41 39 from IPython.zmq.entry_point import write_connection_file
42 40 from session import Session
43 41
44 42 #-----------------------------------------------------------------------------
45 43 # Constants and exceptions
46 44 #-----------------------------------------------------------------------------
47 45
48 46 class InvalidPortNumber(Exception):
49 47 pass
50 48
51 49 #-----------------------------------------------------------------------------
52 50 # Utility functions
53 51 #-----------------------------------------------------------------------------
54 52
55 53 # some utilities to validate message structure, these might get moved elsewhere
56 54 # if they prove to have more generic utility
57 55
58 56 def validate_string_list(lst):
59 57 """Validate that the input is a list of strings.
60 58
61 59 Raises ValueError if not."""
62 60 if not isinstance(lst, list):
63 61 raise ValueError('input %r must be a list' % lst)
64 62 for x in lst:
65 63 if not isinstance(x, basestring):
66 64 raise ValueError('element %r in list must be a string' % x)
67 65
68 66
69 67 def validate_string_dict(dct):
70 68 """Validate that the input is a dict with string keys and values.
71 69
72 70 Raises ValueError if not."""
73 71 for k,v in dct.iteritems():
74 72 if not isinstance(k, basestring):
75 73 raise ValueError('key %r in dict must be a string' % k)
76 74 if not isinstance(v, basestring):
77 75 raise ValueError('value %r in dict must be a string' % v)
78 76
79 77
80 78 #-----------------------------------------------------------------------------
81 79 # ZMQ Socket Channel classes
82 80 #-----------------------------------------------------------------------------
83 81
84 82 class ZMQSocketChannel(Thread):
85 83 """The base class for the channels that use ZMQ sockets.
86 84 """
87 85 context = None
88 86 session = None
89 87 socket = None
90 88 ioloop = None
91 iostate = None
89 stream = None
92 90 _address = None
93 91
94 92 def __init__(self, context, session, address):
95 93 """Create a channel
96 94
97 95 Parameters
98 96 ----------
99 97 context : :class:`zmq.Context`
100 98 The ZMQ context to use.
101 99 session : :class:`session.Session`
102 100 The session to use.
103 101 address : tuple
104 102 Standard (ip, port) tuple that the kernel is listening on.
105 103 """
106 104 super(ZMQSocketChannel, self).__init__()
107 105 self.daemon = True
108 106
109 107 self.context = context
110 108 self.session = session
111 109 if address[1] == 0:
112 110 message = 'The port number for a channel cannot be 0.'
113 111 raise InvalidPortNumber(message)
114 112 self._address = address
115 113
116 114 def _run_loop(self):
117 115 """Run my loop, ignoring EINTR events in the poller"""
118 116 while True:
119 117 try:
120 118 self.ioloop.start()
121 119 except zmq.ZMQError as e:
122 120 if e.errno == errno.EINTR:
123 121 continue
124 122 else:
125 123 raise
126 124 else:
127 125 break
128 126
129 127 def stop(self):
130 128 """Stop the channel's activity.
131 129
132 130 This calls :method:`Thread.join` and returns when the thread
133 131 terminates. :class:`RuntimeError` will be raised if
134 132 :method:`self.start` is called again.
135 133 """
136 134 self.join()
137 135
138 136 @property
139 137 def address(self):
140 138 """Get the channel's address as an (ip, port) tuple.
141 139
142 140 By the default, the address is (localhost, 0), where 0 means a random
143 141 port.
144 142 """
145 143 return self._address
146 144
147 def add_io_state(self, state):
148 """Add IO state to the eventloop.
149
145 def _queue_send(self, msg):
146 """Queue a message to be sent from the IOLoop's thread.
147
150 148 Parameters
151 149 ----------
152 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
153 The IO state flag to set.
154
155 This is thread safe as it uses the thread safe IOLoop.add_callback.
150 msg : message to send
151
152 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
153 thread control of the action.
156 154 """
157 def add_io_state_callback():
158 if not self.iostate & state:
159 self.iostate = self.iostate | state
160 self.ioloop.update_handler(self.socket, self.iostate)
161 self.ioloop.add_callback(add_io_state_callback)
162
163 def drop_io_state(self, state):
164 """Drop IO state from the eventloop.
165
166 Parameters
167 ----------
168 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
169 The IO state flag to set.
155 def thread_send():
156 self.session.send(self.stream, msg)
157 self.ioloop.add_callback(thread_send)
170 158
171 This is thread safe as it uses the thread safe IOLoop.add_callback.
159 def _handle_recv(self, msg):
160 """callback for stream.on_recv
161
162 unpacks message, and calls handlers with it.
172 163 """
173 def drop_io_state_callback():
174 if self.iostate & state:
175 self.iostate = self.iostate & (~state)
176 self.ioloop.update_handler(self.socket, self.iostate)
177 self.ioloop.add_callback(drop_io_state_callback)
164 ident,smsg = self.session.feed_identities(msg)
165 self.call_handlers(self.session.unserialize(smsg))
166
178 167
179 168
180 169 class ShellSocketChannel(ZMQSocketChannel):
181 170 """The XREQ channel for issues request/replies to the kernel.
182 171 """
183 172
184 173 command_queue = None
185 174 # flag for whether execute requests should be allowed to call raw_input:
186 175 allow_stdin = True
187 176
188 177 def __init__(self, context, session, address):
189 178 super(ShellSocketChannel, self).__init__(context, session, address)
190 self.command_queue = Queue()
191 179 self.ioloop = ioloop.IOLoop()
192 180
193 181 def run(self):
194 182 """The thread's main activity. Call start() instead."""
195 183 self.socket = self.context.socket(zmq.DEALER)
196 184 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
197 185 self.socket.connect('tcp://%s:%i' % self.address)
198 self.iostate = POLLERR|POLLIN
199 self.ioloop.add_handler(self.socket, self._handle_events,
200 self.iostate)
186 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
187 self.stream.on_recv(self._handle_recv)
201 188 self._run_loop()
202 189
203 190 def stop(self):
204 191 self.ioloop.stop()
205 192 super(ShellSocketChannel, self).stop()
206 193
207 194 def call_handlers(self, msg):
208 195 """This method is called in the ioloop thread when a message arrives.
209 196
210 197 Subclasses should override this method to handle incoming messages.
211 198 It is important to remember that this method is called in the thread
212 199 so that some logic must be done to ensure that the application leve
213 200 handlers are called in the application thread.
214 201 """
215 202 raise NotImplementedError('call_handlers must be defined in a subclass.')
216 203
217 204 def execute(self, code, silent=False,
218 205 user_variables=None, user_expressions=None, allow_stdin=None):
219 206 """Execute code in the kernel.
220 207
221 208 Parameters
222 209 ----------
223 210 code : str
224 211 A string of Python code.
225 212
226 213 silent : bool, optional (default False)
227 214 If set, the kernel will execute the code as quietly possible.
228 215
229 216 user_variables : list, optional
230 217 A list of variable names to pull from the user's namespace. They
231 218 will come back as a dict with these names as keys and their
232 219 :func:`repr` as values.
233 220
234 221 user_expressions : dict, optional
235 222 A dict with string keys and to pull from the user's
236 223 namespace. They will come back as a dict with these names as keys
237 224 and their :func:`repr` as values.
238 225
239 226 allow_stdin : bool, optional
240 227 Flag for
241 228 A dict with string keys and to pull from the user's
242 229 namespace. They will come back as a dict with these names as keys
243 230 and their :func:`repr` as values.
244 231
245 232 Returns
246 233 -------
247 234 The msg_id of the message sent.
248 235 """
249 236 if user_variables is None:
250 237 user_variables = []
251 238 if user_expressions is None:
252 239 user_expressions = {}
253 240 if allow_stdin is None:
254 241 allow_stdin = self.allow_stdin
255 242
256 243
257 244 # Don't waste network traffic if inputs are invalid
258 245 if not isinstance(code, basestring):
259 246 raise ValueError('code %r must be a string' % code)
260 247 validate_string_list(user_variables)
261 248 validate_string_dict(user_expressions)
262 249
263 250 # Create class for content/msg creation. Related to, but possibly
264 251 # not in Session.
265 252 content = dict(code=code, silent=silent,
266 253 user_variables=user_variables,
267 254 user_expressions=user_expressions,
268 255 allow_stdin=allow_stdin,
269 256 )
270 257 msg = self.session.msg('execute_request', content)
271 self._queue_request(msg)
258 self._queue_send(msg)
272 259 return msg['header']['msg_id']
273 260
274 261 def complete(self, text, line, cursor_pos, block=None):
275 262 """Tab complete text in the kernel's namespace.
276 263
277 264 Parameters
278 265 ----------
279 266 text : str
280 267 The text to complete.
281 268 line : str
282 269 The full line of text that is the surrounding context for the
283 270 text to complete.
284 271 cursor_pos : int
285 272 The position of the cursor in the line where the completion was
286 273 requested.
287 274 block : str, optional
288 275 The full block of code in which the completion is being requested.
289 276
290 277 Returns
291 278 -------
292 279 The msg_id of the message sent.
293 280 """
294 281 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
295 282 msg = self.session.msg('complete_request', content)
296 self._queue_request(msg)
283 self._queue_send(msg)
297 284 return msg['header']['msg_id']
298 285
299 286 def object_info(self, oname):
300 287 """Get metadata information about an object.
301 288
302 289 Parameters
303 290 ----------
304 291 oname : str
305 292 A string specifying the object name.
306 293
307 294 Returns
308 295 -------
309 296 The msg_id of the message sent.
310 297 """
311 298 content = dict(oname=oname)
312 299 msg = self.session.msg('object_info_request', content)
313 self._queue_request(msg)
300 self._queue_send(msg)
314 301 return msg['header']['msg_id']
315 302
316 303 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
317 304 """Get entries from the history list.
318 305
319 306 Parameters
320 307 ----------
321 308 raw : bool
322 309 If True, return the raw input.
323 310 output : bool
324 311 If True, then return the output as well.
325 312 hist_access_type : str
326 313 'range' (fill in session, start and stop params), 'tail' (fill in n)
327 314 or 'search' (fill in pattern param).
328 315
329 316 session : int
330 317 For a range request, the session from which to get lines. Session
331 318 numbers are positive integers; negative ones count back from the
332 319 current session.
333 320 start : int
334 321 The first line number of a history range.
335 322 stop : int
336 323 The final (excluded) line number of a history range.
337 324
338 325 n : int
339 326 The number of lines of history to get for a tail request.
340 327
341 328 pattern : str
342 329 The glob-syntax pattern for a search request.
343 330
344 331 Returns
345 332 -------
346 333 The msg_id of the message sent.
347 334 """
348 335 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
349 336 **kwargs)
350 337 msg = self.session.msg('history_request', content)
351 self._queue_request(msg)
338 self._queue_send(msg)
352 339 return msg['header']['msg_id']
353 340
354 341 def shutdown(self, restart=False):
355 342 """Request an immediate kernel shutdown.
356 343
357 344 Upon receipt of the (empty) reply, client code can safely assume that
358 345 the kernel has shut down and it's safe to forcefully terminate it if
359 346 it's still alive.
360 347
361 348 The kernel will send the reply via a function registered with Python's
362 349 atexit module, ensuring it's truly done as the kernel is done with all
363 350 normal operation.
364 351 """
365 352 # Send quit message to kernel. Once we implement kernel-side setattr,
366 353 # this should probably be done that way, but for now this will do.
367 354 msg = self.session.msg('shutdown_request', {'restart':restart})
368 self._queue_request(msg)
355 self._queue_send(msg)
369 356 return msg['header']['msg_id']
370 357
371 def _handle_events(self, socket, events):
372 if events & POLLERR:
373 self._handle_err()
374 if events & POLLOUT:
375 self._handle_send()
376 if events & POLLIN:
377 self._handle_recv()
378
379 def _handle_recv(self):
380 ident,msg = self.session.recv(self.socket, 0)
381 self.call_handlers(msg)
382
383 def _handle_send(self):
384 try:
385 msg = self.command_queue.get(False)
386 except Empty:
387 pass
388 else:
389 self.session.send(self.socket,msg)
390 if self.command_queue.empty():
391 self.drop_io_state(POLLOUT)
392
393 def _handle_err(self):
394 # We don't want to let this go silently, so eventually we should log.
395 raise zmq.ZMQError()
396
397 def _queue_request(self, msg):
398 self.command_queue.put(msg)
399 self.add_io_state(POLLOUT)
400 358
401 359
402 360 class SubSocketChannel(ZMQSocketChannel):
403 361 """The SUB channel which listens for messages that the kernel publishes.
404 362 """
405 363
406 364 def __init__(self, context, session, address):
407 365 super(SubSocketChannel, self).__init__(context, session, address)
408 366 self.ioloop = ioloop.IOLoop()
409 367
410 368 def run(self):
411 369 """The thread's main activity. Call start() instead."""
412 370 self.socket = self.context.socket(zmq.SUB)
413 371 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
414 372 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
415 373 self.socket.connect('tcp://%s:%i' % self.address)
416 self.iostate = POLLIN|POLLERR
417 self.ioloop.add_handler(self.socket, self._handle_events,
418 self.iostate)
374 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
375 self.stream.on_recv(self._handle_recv)
419 376 self._run_loop()
420 377
421 378 def stop(self):
422 379 self.ioloop.stop()
423 380 super(SubSocketChannel, self).stop()
424 381
425 382 def call_handlers(self, msg):
426 383 """This method is called in the ioloop thread when a message arrives.
427 384
428 385 Subclasses should override this method to handle incoming messages.
429 386 It is important to remember that this method is called in the thread
430 387 so that some logic must be done to ensure that the application leve
431 388 handlers are called in the application thread.
432 389 """
433 390 raise NotImplementedError('call_handlers must be defined in a subclass.')
434 391
435 392 def flush(self, timeout=1.0):
436 393 """Immediately processes all pending messages on the SUB channel.
437 394
438 395 Callers should use this method to ensure that :method:`call_handlers`
439 396 has been called for all messages that have been received on the
440 397 0MQ SUB socket of this channel.
441 398
442 399 This method is thread safe.
443 400
444 401 Parameters
445 402 ----------
446 403 timeout : float, optional
447 404 The maximum amount of time to spend flushing, in seconds. The
448 405 default is one second.
449 406 """
450 407 # We do the IOLoop callback process twice to ensure that the IOLoop
451 408 # gets to perform at least one full poll.
452 409 stop_time = time.time() + timeout
453 410 for i in xrange(2):
454 411 self._flushed = False
455 412 self.ioloop.add_callback(self._flush)
456 413 while not self._flushed and time.time() < stop_time:
457 414 time.sleep(0.01)
458 415
459 def _handle_events(self, socket, events):
460 # Turn on and off POLLOUT depending on if we have made a request
461 if events & POLLERR:
462 self._handle_err()
463 if events & POLLIN:
464 self._handle_recv()
465
466 def _handle_err(self):
467 # We don't want to let this go silently, so eventually we should log.
468 raise zmq.ZMQError()
469
470 def _handle_recv(self):
471 # Get all of the messages we can
472 while True:
473 try:
474 ident,msg = self.session.recv(self.socket)
475 except zmq.ZMQError:
476 # Check the errno?
477 # Will this trigger POLLERR?
478 break
479 else:
480 if msg is None:
481 break
482 self.call_handlers(msg)
483
484 416 def _flush(self):
485 417 """Callback for :method:`self.flush`."""
418 self.stream.flush()
486 419 self._flushed = True
487 420
488 421
489 422 class StdInSocketChannel(ZMQSocketChannel):
490 423 """A reply channel to handle raw_input requests that the kernel makes."""
491 424
492 425 msg_queue = None
493 426
494 427 def __init__(self, context, session, address):
495 428 super(StdInSocketChannel, self).__init__(context, session, address)
496 429 self.ioloop = ioloop.IOLoop()
497 self.msg_queue = Queue()
498 430
499 431 def run(self):
500 432 """The thread's main activity. Call start() instead."""
501 433 self.socket = self.context.socket(zmq.DEALER)
502 434 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
503 435 self.socket.connect('tcp://%s:%i' % self.address)
504 self.iostate = POLLERR|POLLIN
505 self.ioloop.add_handler(self.socket, self._handle_events,
506 self.iostate)
436 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
437 self.stream.on_recv(self._handle_recv)
507 438 self._run_loop()
508 439
509 440 def stop(self):
510 441 self.ioloop.stop()
511 442 super(StdInSocketChannel, self).stop()
512 443
513 444 def call_handlers(self, msg):
514 445 """This method is called in the ioloop thread when a message arrives.
515 446
516 447 Subclasses should override this method to handle incoming messages.
517 448 It is important to remember that this method is called in the thread
518 449 so that some logic must be done to ensure that the application leve
519 450 handlers are called in the application thread.
520 451 """
521 452 raise NotImplementedError('call_handlers must be defined in a subclass.')
522 453
523 454 def input(self, string):
524 455 """Send a string of raw input to the kernel."""
525 456 content = dict(value=string)
526 457 msg = self.session.msg('input_reply', content)
527 self._queue_reply(msg)
528
529 def _handle_events(self, socket, events):
530 if events & POLLERR:
531 self._handle_err()
532 if events & POLLOUT:
533 self._handle_send()
534 if events & POLLIN:
535 self._handle_recv()
536
537 def _handle_recv(self):
538 ident,msg = self.session.recv(self.socket, 0)
539 self.call_handlers(msg)
540
541 def _handle_send(self):
542 try:
543 msg = self.msg_queue.get(False)
544 except Empty:
545 pass
546 else:
547 self.session.send(self.socket,msg)
548 if self.msg_queue.empty():
549 self.drop_io_state(POLLOUT)
550
551 def _handle_err(self):
552 # We don't want to let this go silently, so eventually we should log.
553 raise zmq.ZMQError()
554
555 def _queue_reply(self, msg):
556 self.msg_queue.put(msg)
557 self.add_io_state(POLLOUT)
458 self._queue_send(msg)
558 459
559 460
560 461 class HBSocketChannel(ZMQSocketChannel):
561 462 """The heartbeat channel which monitors the kernel heartbeat.
562 463
563 464 Note that the heartbeat channel is paused by default. As long as you start
564 465 this channel, the kernel manager will ensure that it is paused and un-paused
565 466 as appropriate.
566 467 """
567 468
568 469 time_to_dead = 3.0
569 470 socket = None
570 471 poller = None
571 472 _running = None
572 473 _pause = None
573 474
574 475 def __init__(self, context, session, address):
575 476 super(HBSocketChannel, self).__init__(context, session, address)
576 477 self._running = False
577 478 self._pause = True
578 479
579 480 def _create_socket(self):
580 481 self.socket = self.context.socket(zmq.REQ)
581 482 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
582 483 self.socket.connect('tcp://%s:%i' % self.address)
583 484 self.poller = zmq.Poller()
584 485 self.poller.register(self.socket, zmq.POLLIN)
585 486
586 487 def run(self):
587 488 """The thread's main activity. Call start() instead."""
588 489 self._create_socket()
589 490 self._running = True
590 491 while self._running:
591 492 if self._pause:
592 493 time.sleep(self.time_to_dead)
593 494 else:
594 495 since_last_heartbeat = 0.0
595 496 request_time = time.time()
596 497 try:
597 498 #io.rprint('Ping from HB channel') # dbg
598 499 self.socket.send(b'ping')
599 500 except zmq.ZMQError, e:
600 501 #io.rprint('*** HB Error:', e) # dbg
601 502 if e.errno == zmq.EFSM:
602 503 #io.rprint('sleep...', self.time_to_dead) # dbg
603 504 time.sleep(self.time_to_dead)
604 505 self._create_socket()
605 506 else:
606 507 raise
607 508 else:
608 509 while True:
609 510 try:
610 511 self.socket.recv(zmq.NOBLOCK)
611 512 except zmq.ZMQError, e:
612 513 #io.rprint('*** HB Error 2:', e) # dbg
613 514 if e.errno == zmq.EAGAIN:
614 515 before_poll = time.time()
615 516 until_dead = self.time_to_dead - (before_poll -
616 517 request_time)
617 518
618 519 # When the return value of poll() is an empty
619 520 # list, that is when things have gone wrong
620 521 # (zeromq bug). As long as it is not an empty
621 522 # list, poll is working correctly even if it
622 523 # returns quickly. Note: poll timeout is in
623 524 # milliseconds.
624 525 if until_dead > 0.0:
625 526 while True:
626 527 try:
627 528 self.poller.poll(1000 * until_dead)
628 529 except zmq.ZMQError as e:
629 530 if e.errno == errno.EINTR:
630 531 continue
631 532 else:
632 533 raise
633 534 else:
634 535 break
635 536
636 537 since_last_heartbeat = time.time()-request_time
637 538 if since_last_heartbeat > self.time_to_dead:
638 539 self.call_handlers(since_last_heartbeat)
639 540 break
640 541 else:
641 542 # FIXME: We should probably log this instead.
642 543 raise
643 544 else:
644 545 until_dead = self.time_to_dead - (time.time() -
645 546 request_time)
646 547 if until_dead > 0.0:
647 548 #io.rprint('sleep...', self.time_to_dead) # dbg
648 549 time.sleep(until_dead)
649 550 break
650 551
651 552 def pause(self):
652 553 """Pause the heartbeat."""
653 554 self._pause = True
654 555
655 556 def unpause(self):
656 557 """Unpause the heartbeat."""
657 558 self._pause = False
658 559
659 560 def is_beating(self):
660 561 """Is the heartbeat running and not paused."""
661 562 if self.is_alive() and not self._pause:
662 563 return True
663 564 else:
664 565 return False
665 566
666 567 def stop(self):
667 568 self._running = False
668 569 super(HBSocketChannel, self).stop()
669 570
670 571 def call_handlers(self, since_last_heartbeat):
671 572 """This method is called in the ioloop thread when a message arrives.
672 573
673 574 Subclasses should override this method to handle incoming messages.
674 575 It is important to remember that this method is called in the thread
675 576 so that some logic must be done to ensure that the application leve
676 577 handlers are called in the application thread.
677 578 """
678 579 raise NotImplementedError('call_handlers must be defined in a subclass.')
679 580
680 581
681 582 #-----------------------------------------------------------------------------
682 583 # Main kernel manager class
683 584 #-----------------------------------------------------------------------------
684 585
685 586 class KernelManager(HasTraits):
686 587 """ Manages a kernel for a frontend.
687 588
688 589 The SUB channel is for the frontend to receive messages published by the
689 590 kernel.
690 591
691 592 The REQ channel is for the frontend to make requests of the kernel.
692 593
693 594 The REP channel is for the kernel to request stdin (raw_input) from the
694 595 frontend.
695 596 """
696 597 # config object for passing to child configurables
697 598 config = Instance(Config)
698 599
699 600 # The PyZMQ Context to use for communication with the kernel.
700 601 context = Instance(zmq.Context)
701 602 def _context_default(self):
702 603 return zmq.Context.instance()
703 604
704 605 # The Session to use for communication with the kernel.
705 606 session = Instance(Session)
706 607
707 608 # The kernel process with which the KernelManager is communicating.
708 609 kernel = Instance(Popen)
709 610
710 611 # The addresses for the communication channels.
711 612 connection_file = Unicode('')
712 613 ip = Unicode(LOCALHOST)
713 614 def _ip_changed(self, name, old, new):
714 615 if new == '*':
715 616 self.ip = '0.0.0.0'
716 617 shell_port = Integer(0)
717 618 iopub_port = Integer(0)
718 619 stdin_port = Integer(0)
719 620 hb_port = Integer(0)
720 621
721 622 # The classes to use for the various channels.
722 623 shell_channel_class = Type(ShellSocketChannel)
723 624 sub_channel_class = Type(SubSocketChannel)
724 625 stdin_channel_class = Type(StdInSocketChannel)
725 626 hb_channel_class = Type(HBSocketChannel)
726 627
727 628 # Protected traits.
728 629 _launch_args = Any
729 630 _shell_channel = Any
730 631 _sub_channel = Any
731 632 _stdin_channel = Any
732 633 _hb_channel = Any
733 634 _connection_file_written=Bool(False)
734 635
735 636 def __init__(self, **kwargs):
736 637 super(KernelManager, self).__init__(**kwargs)
737 638 if self.session is None:
738 639 self.session = Session(config=self.config)
739 640
740 641 def __del__(self):
741 642 if self._connection_file_written:
742 643 # cleanup connection files on full shutdown of kernel we started
743 644 self._connection_file_written = False
744 645 try:
745 646 os.remove(self.connection_file)
746 647 except IOError:
747 648 pass
748 649
749 650
750 651 #--------------------------------------------------------------------------
751 652 # Channel management methods:
752 653 #--------------------------------------------------------------------------
753 654
754 655 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
755 656 """Starts the channels for this kernel.
756 657
757 658 This will create the channels if they do not exist and then start
758 659 them. If port numbers of 0 are being used (random ports) then you
759 660 must first call :method:`start_kernel`. If the channels have been
760 661 stopped and you call this, :class:`RuntimeError` will be raised.
761 662 """
762 663 if shell:
763 664 self.shell_channel.start()
764 665 if sub:
765 666 self.sub_channel.start()
766 667 if stdin:
767 668 self.stdin_channel.start()
768 669 self.shell_channel.allow_stdin = True
769 670 else:
770 671 self.shell_channel.allow_stdin = False
771 672 if hb:
772 673 self.hb_channel.start()
773 674
774 675 def stop_channels(self):
775 676 """Stops all the running channels for this kernel.
776 677 """
777 678 if self.shell_channel.is_alive():
778 679 self.shell_channel.stop()
779 680 if self.sub_channel.is_alive():
780 681 self.sub_channel.stop()
781 682 if self.stdin_channel.is_alive():
782 683 self.stdin_channel.stop()
783 684 if self.hb_channel.is_alive():
784 685 self.hb_channel.stop()
785 686
786 687 @property
787 688 def channels_running(self):
788 689 """Are any of the channels created and running?"""
789 690 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
790 691 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
791 692
792 693 #--------------------------------------------------------------------------
793 694 # Kernel process management methods:
794 695 #--------------------------------------------------------------------------
795 696
796 697 def load_connection_file(self):
797 698 """load connection info from JSON dict in self.connection_file"""
798 699 with open(self.connection_file) as f:
799 700 cfg = json.loads(f.read())
800 701
801 702 self.ip = cfg['ip']
802 703 self.shell_port = cfg['shell_port']
803 704 self.stdin_port = cfg['stdin_port']
804 705 self.iopub_port = cfg['iopub_port']
805 706 self.hb_port = cfg['hb_port']
806 707 self.session.key = str_to_bytes(cfg['key'])
807 708
808 709 def write_connection_file(self):
809 710 """write connection info to JSON dict in self.connection_file"""
810 711 if self._connection_file_written:
811 712 return
812 713 self.connection_file,cfg = write_connection_file(self.connection_file,
813 714 ip=self.ip, key=self.session.key,
814 715 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
815 716 shell_port=self.shell_port, hb_port=self.hb_port)
816 717 # write_connection_file also sets default ports:
817 718 self.shell_port = cfg['shell_port']
818 719 self.stdin_port = cfg['stdin_port']
819 720 self.iopub_port = cfg['iopub_port']
820 721 self.hb_port = cfg['hb_port']
821 722
822 723 self._connection_file_written = True
823 724
824 725 def start_kernel(self, **kw):
825 726 """Starts a kernel process and configures the manager to use it.
826 727
827 728 If random ports (port=0) are being used, this method must be called
828 729 before the channels are created.
829 730
830 731 Parameters:
831 732 -----------
832 733 ipython : bool, optional (default True)
833 734 Whether to use an IPython kernel instead of a plain Python kernel.
834 735
835 736 launcher : callable, optional (default None)
836 737 A custom function for launching the kernel process (generally a
837 738 wrapper around ``entry_point.base_launch_kernel``). In most cases,
838 739 it should not be necessary to use this parameter.
839 740
840 741 **kw : optional
841 742 See respective options for IPython and Python kernels.
842 743 """
843 744 if self.ip not in LOCAL_IPS:
844 745 raise RuntimeError("Can only launch a kernel on a local interface. "
845 746 "Make sure that the '*_address' attributes are "
846 747 "configured properly. "
847 748 "Currently valid addresses are: %s"%LOCAL_IPS
848 749 )
849 750
850 751 # write connection file / get default ports
851 752 self.write_connection_file()
852 753
853 754 self._launch_args = kw.copy()
854 755 launch_kernel = kw.pop('launcher', None)
855 756 if launch_kernel is None:
856 757 if kw.pop('ipython', True):
857 758 from ipkernel import launch_kernel
858 759 else:
859 760 from pykernel import launch_kernel
860 761 self.kernel = launch_kernel(fname=self.connection_file, **kw)
861 762
862 763 def shutdown_kernel(self, restart=False):
863 764 """ Attempts to the stop the kernel process cleanly. If the kernel
864 765 cannot be stopped, it is killed, if possible.
865 766 """
866 767 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
867 768 if sys.platform == 'win32':
868 769 self.kill_kernel()
869 770 return
870 771
871 772 # Pause the heart beat channel if it exists.
872 773 if self._hb_channel is not None:
873 774 self._hb_channel.pause()
874 775
875 776 # Don't send any additional kernel kill messages immediately, to give
876 777 # the kernel a chance to properly execute shutdown actions. Wait for at
877 778 # most 1s, checking every 0.1s.
878 779 self.shell_channel.shutdown(restart=restart)
879 780 for i in range(10):
880 781 if self.is_alive:
881 782 time.sleep(0.1)
882 783 else:
883 784 break
884 785 else:
885 786 # OK, we've waited long enough.
886 787 if self.has_kernel:
887 788 self.kill_kernel()
888 789
889 790 if not restart and self._connection_file_written:
890 791 # cleanup connection files on full shutdown of kernel we started
891 792 self._connection_file_written = False
892 793 try:
893 794 os.remove(self.connection_file)
894 795 except IOError:
895 796 pass
896 797
897 798 def restart_kernel(self, now=False, **kw):
898 799 """Restarts a kernel with the arguments that were used to launch it.
899 800
900 801 If the old kernel was launched with random ports, the same ports will be
901 802 used for the new kernel.
902 803
903 804 Parameters
904 805 ----------
905 806 now : bool, optional
906 807 If True, the kernel is forcefully restarted *immediately*, without
907 808 having a chance to do any cleanup action. Otherwise the kernel is
908 809 given 1s to clean up before a forceful restart is issued.
909 810
910 811 In all cases the kernel is restarted, the only difference is whether
911 812 it is given a chance to perform a clean shutdown or not.
912 813
913 814 **kw : optional
914 815 Any options specified here will replace those used to launch the
915 816 kernel.
916 817 """
917 818 if self._launch_args is None:
918 819 raise RuntimeError("Cannot restart the kernel. "
919 820 "No previous call to 'start_kernel'.")
920 821 else:
921 822 # Stop currently running kernel.
922 823 if self.has_kernel:
923 824 if now:
924 825 self.kill_kernel()
925 826 else:
926 827 self.shutdown_kernel(restart=True)
927 828
928 829 # Start new kernel.
929 830 self._launch_args.update(kw)
930 831 self.start_kernel(**self._launch_args)
931 832
932 833 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
933 834 # unless there is some delay here.
934 835 if sys.platform == 'win32':
935 836 time.sleep(0.2)
936 837
937 838 @property
938 839 def has_kernel(self):
939 840 """Returns whether a kernel process has been specified for the kernel
940 841 manager.
941 842 """
942 843 return self.kernel is not None
943 844
944 845 def kill_kernel(self):
945 846 """ Kill the running kernel. """
946 847 if self.has_kernel:
947 848 # Pause the heart beat channel if it exists.
948 849 if self._hb_channel is not None:
949 850 self._hb_channel.pause()
950 851
951 852 # Attempt to kill the kernel.
952 853 try:
953 854 self.kernel.kill()
954 855 except OSError, e:
955 856 # In Windows, we will get an Access Denied error if the process
956 857 # has already terminated. Ignore it.
957 858 if sys.platform == 'win32':
958 859 if e.winerror != 5:
959 860 raise
960 861 # On Unix, we may get an ESRCH error if the process has already
961 862 # terminated. Ignore it.
962 863 else:
963 864 from errno import ESRCH
964 865 if e.errno != ESRCH:
965 866 raise
966 867 self.kernel = None
967 868 else:
968 869 raise RuntimeError("Cannot kill kernel. No kernel is running!")
969 870
970 871 def interrupt_kernel(self):
971 872 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
972 873 well supported on all platforms.
973 874 """
974 875 if self.has_kernel:
975 876 if sys.platform == 'win32':
976 877 from parentpoller import ParentPollerWindows as Poller
977 878 Poller.send_interrupt(self.kernel.win32_interrupt_event)
978 879 else:
979 880 self.kernel.send_signal(signal.SIGINT)
980 881 else:
981 882 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
982 883
983 884 def signal_kernel(self, signum):
984 885 """ Sends a signal to the kernel. Note that since only SIGTERM is
985 886 supported on Windows, this function is only useful on Unix systems.
986 887 """
987 888 if self.has_kernel:
988 889 self.kernel.send_signal(signum)
989 890 else:
990 891 raise RuntimeError("Cannot signal kernel. No kernel is running!")
991 892
992 893 @property
993 894 def is_alive(self):
994 895 """Is the kernel process still running?"""
995 896 # FIXME: not using a heartbeat means this method is broken for any
996 897 # remote kernel, it's only capable of handling local kernels.
997 898 if self.has_kernel:
998 899 if self.kernel.poll() is None:
999 900 return True
1000 901 else:
1001 902 return False
1002 903 else:
1003 904 # We didn't start the kernel with this KernelManager so we don't
1004 905 # know if it is running. We should use a heartbeat for this case.
1005 906 return True
1006 907
1007 908 #--------------------------------------------------------------------------
1008 909 # Channels used for communication with the kernel:
1009 910 #--------------------------------------------------------------------------
1010 911
1011 912 @property
1012 913 def shell_channel(self):
1013 914 """Get the REQ socket channel object to make requests of the kernel."""
1014 915 if self._shell_channel is None:
1015 916 self._shell_channel = self.shell_channel_class(self.context,
1016 917 self.session,
1017 918 (self.ip, self.shell_port))
1018 919 return self._shell_channel
1019 920
1020 921 @property
1021 922 def sub_channel(self):
1022 923 """Get the SUB socket channel object."""
1023 924 if self._sub_channel is None:
1024 925 self._sub_channel = self.sub_channel_class(self.context,
1025 926 self.session,
1026 927 (self.ip, self.iopub_port))
1027 928 return self._sub_channel
1028 929
1029 930 @property
1030 931 def stdin_channel(self):
1031 932 """Get the REP socket channel object to handle stdin (raw_input)."""
1032 933 if self._stdin_channel is None:
1033 934 self._stdin_channel = self.stdin_channel_class(self.context,
1034 935 self.session,
1035 936 (self.ip, self.stdin_port))
1036 937 return self._stdin_channel
1037 938
1038 939 @property
1039 940 def hb_channel(self):
1040 941 """Get the heartbeat socket channel object to check that the
1041 942 kernel is alive."""
1042 943 if self._hb_channel is None:
1043 944 self._hb_channel = self.hb_channel_class(self.context,
1044 945 self.session,
1045 946 (self.ip, self.hb_port))
1046 947 return self._hb_channel
General Comments 0
You need to be logged in to leave comments. Login now