##// END OF EJS Templates
KernelManager has port traits instead of multiple ip/port pairs...
MinRK -
Show More
@@ -1,503 +1,503 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib imports
20 20 import os
21 21 import signal
22 22 import sys
23 23 from getpass import getpass
24 24
25 25 # System library imports
26 26 from IPython.external.qt import QtGui
27 27 from pygments.styles import get_all_styles
28 28
29 29 # external imports
30 30 from IPython.external.ssh import tunnel
31 31
32 32 # Local imports
33 33 from IPython.config.application import boolean_flag
34 34 from IPython.core.application import BaseIPythonApplication
35 35 from IPython.core.profiledir import ProfileDir
36 36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
37 37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
38 38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
39 39 from IPython.frontend.qt.console import styles
40 40 from IPython.frontend.qt.kernelmanager import QtKernelManager
41 41 from IPython.parallel.util import select_random_ports
42 42 from IPython.utils.traitlets import (
43 43 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
44 44 )
45 45 from IPython.zmq.ipkernel import (
46 46 flags as ipkernel_flags,
47 47 aliases as ipkernel_aliases,
48 48 IPKernelApp
49 49 )
50 50 from IPython.zmq.session import Session
51 51 from IPython.zmq.zmqshell import ZMQInteractiveShell
52 52
53 53
54 54 #-----------------------------------------------------------------------------
55 55 # Network Constants
56 56 #-----------------------------------------------------------------------------
57 57
58 58 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
59 59
60 60 #-----------------------------------------------------------------------------
61 61 # Globals
62 62 #-----------------------------------------------------------------------------
63 63
64 64 _examples = """
65 65 ipython qtconsole # start the qtconsole
66 66 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
67 67 """
68 68
69 69 #-----------------------------------------------------------------------------
70 70 # Classes
71 71 #-----------------------------------------------------------------------------
72 72
73 73 class MainWindow(QtGui.QMainWindow):
74 74
75 75 #---------------------------------------------------------------------------
76 76 # 'object' interface
77 77 #---------------------------------------------------------------------------
78 78
79 79 def __init__(self, app, frontend, existing=False, may_close=True,
80 80 confirm_exit=True):
81 81 """ Create a MainWindow for the specified FrontendWidget.
82 82
83 83 The app is passed as an argument to allow for different
84 84 closing behavior depending on whether we are the Kernel's parent.
85 85
86 86 If existing is True, then this Console does not own the Kernel.
87 87
88 88 If may_close is True, then this Console is permitted to close the kernel
89 89 """
90 90 super(MainWindow, self).__init__()
91 91 self._app = app
92 92 self._frontend = frontend
93 93 self._existing = existing
94 94 if existing:
95 95 self._may_close = may_close
96 96 else:
97 97 self._may_close = True
98 98 self._frontend.exit_requested.connect(self.close)
99 99 self._confirm_exit = confirm_exit
100 100 self.setCentralWidget(frontend)
101 101
102 102 #---------------------------------------------------------------------------
103 103 # QWidget interface
104 104 #---------------------------------------------------------------------------
105 105
106 106 def closeEvent(self, event):
107 107 """ Close the window and the kernel (if necessary).
108 108
109 109 This will prompt the user if they are finished with the kernel, and if
110 110 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
111 111 it closes without prompt.
112 112 """
113 113 keepkernel = None #Use the prompt by default
114 114 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
115 115 keepkernel = self._frontend._keep_kernel_on_exit
116 116
117 117 kernel_manager = self._frontend.kernel_manager
118 118
119 119 if keepkernel is None and not self._confirm_exit:
120 120 # don't prompt, just terminate the kernel if we own it
121 121 # or leave it alone if we don't
122 122 keepkernel = not self._existing
123 123
124 124 if keepkernel is None: #show prompt
125 125 if kernel_manager and kernel_manager.channels_running:
126 126 title = self.window().windowTitle()
127 127 cancel = QtGui.QMessageBox.Cancel
128 128 okay = QtGui.QMessageBox.Ok
129 129 if self._may_close:
130 130 msg = "You are closing this Console window."
131 131 info = "Would you like to quit the Kernel and all attached Consoles as well?"
132 132 justthis = QtGui.QPushButton("&No, just this Console", self)
133 133 justthis.setShortcut('N')
134 134 closeall = QtGui.QPushButton("&Yes, quit everything", self)
135 135 closeall.setShortcut('Y')
136 136 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
137 137 title, msg)
138 138 box.setInformativeText(info)
139 139 box.addButton(cancel)
140 140 box.addButton(justthis, QtGui.QMessageBox.NoRole)
141 141 box.addButton(closeall, QtGui.QMessageBox.YesRole)
142 142 box.setDefaultButton(closeall)
143 143 box.setEscapeButton(cancel)
144 144 reply = box.exec_()
145 145 if reply == 1: # close All
146 146 kernel_manager.shutdown_kernel()
147 147 #kernel_manager.stop_channels()
148 148 event.accept()
149 149 elif reply == 0: # close Console
150 150 if not self._existing:
151 151 # Have kernel: don't quit, just close the window
152 152 self._app.setQuitOnLastWindowClosed(False)
153 153 self.deleteLater()
154 154 event.accept()
155 155 else:
156 156 event.ignore()
157 157 else:
158 158 reply = QtGui.QMessageBox.question(self, title,
159 159 "Are you sure you want to close this Console?"+
160 160 "\nThe Kernel and other Consoles will remain active.",
161 161 okay|cancel,
162 162 defaultButton=okay
163 163 )
164 164 if reply == okay:
165 165 event.accept()
166 166 else:
167 167 event.ignore()
168 168 elif keepkernel: #close console but leave kernel running (no prompt)
169 169 if kernel_manager and kernel_manager.channels_running:
170 170 if not self._existing:
171 171 # I have the kernel: don't quit, just close the window
172 172 self._app.setQuitOnLastWindowClosed(False)
173 173 event.accept()
174 174 else: #close console and kernel (no prompt)
175 175 if kernel_manager and kernel_manager.channels_running:
176 176 kernel_manager.shutdown_kernel()
177 177 event.accept()
178 178
179 179 #-----------------------------------------------------------------------------
180 180 # Aliases and Flags
181 181 #-----------------------------------------------------------------------------
182 182
183 183 flags = dict(ipkernel_flags)
184 184 qt_flags = {
185 185 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
186 186 "Connect to an existing kernel."),
187 187 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
188 188 "Use a pure Python kernel instead of an IPython kernel."),
189 189 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
190 190 "Disable rich text support."),
191 191 }
192 192 qt_flags.update(boolean_flag(
193 193 'gui-completion', 'ConsoleWidget.gui_completion',
194 194 "use a GUI widget for tab completion",
195 195 "use plaintext output for completion"
196 196 ))
197 197 qt_flags.update(boolean_flag(
198 198 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
199 199 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
200 200 to force a direct exit without any confirmation.
201 201 """,
202 202 """Don't prompt the user when exiting. This will terminate the kernel
203 203 if it is owned by the frontend, and leave it alive if it is external.
204 204 """
205 205 ))
206 206 flags.update(qt_flags)
207 207 # the flags that are specific to the frontend
208 208 # these must be scrubbed before being passed to the kernel,
209 209 # or it will raise an error on unrecognized flags
210 210 qt_flags = qt_flags.keys()
211 211
212 212 aliases = dict(ipkernel_aliases)
213 213
214 214 qt_aliases = dict(
215 215 hb = 'IPythonQtConsoleApp.hb_port',
216 216 shell = 'IPythonQtConsoleApp.shell_port',
217 217 iopub = 'IPythonQtConsoleApp.iopub_port',
218 218 stdin = 'IPythonQtConsoleApp.stdin_port',
219 219 ip = 'IPythonQtConsoleApp.ip',
220 220
221 221 style = 'IPythonWidget.syntax_style',
222 222 stylesheet = 'IPythonQtConsoleApp.stylesheet',
223 223 colors = 'ZMQInteractiveShell.colors',
224 224
225 225 editor = 'IPythonWidget.editor',
226 226 paging = 'ConsoleWidget.paging',
227 227 ssh = 'IPythonQtConsoleApp.sshserver',
228 228 )
229 229 aliases.update(qt_aliases)
230 230 # also scrub aliases from the frontend
231 231 qt_flags.extend(qt_aliases.keys())
232 232
233 233
234 234 #-----------------------------------------------------------------------------
235 235 # IPythonQtConsole
236 236 #-----------------------------------------------------------------------------
237 237
238 238
239 239 class IPythonQtConsoleApp(BaseIPythonApplication):
240 240 name = 'ipython-qtconsole'
241 241 default_config_file_name='ipython_config.py'
242 242
243 243 description = """
244 244 The IPython QtConsole.
245 245
246 246 This launches a Console-style application using Qt. It is not a full
247 247 console, in that launched terminal subprocesses will not be able to accept
248 248 input.
249 249
250 250 The QtConsole supports various extra features beyond the Terminal IPython
251 251 shell, such as inline plotting with matplotlib, via:
252 252
253 253 ipython qtconsole --pylab=inline
254 254
255 255 as well as saving your session as HTML, and printing the output.
256 256
257 257 """
258 258 examples = _examples
259 259
260 260 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
261 261 flags = Dict(flags)
262 262 aliases = Dict(aliases)
263 263
264 264 kernel_argv = List(Unicode)
265 265
266 266 # create requested profiles by default, if they don't exist:
267 267 auto_create = CBool(True)
268 268 # connection info:
269 269 ip = Unicode(LOCALHOST, config=True,
270 270 help="""Set the kernel\'s IP address [default localhost].
271 271 If the IP address is something other than localhost, then
272 272 Consoles on other machines will be able to connect
273 273 to the Kernel, so be careful!"""
274 274 )
275 275
276 276 sshserver = Unicode('', config=True,
277 277 help="""The SSH server to use to connect to the kernel.""")
278 278 sshkey = Unicode('', config=True,
279 279 help="""Path to the ssh key to use for logging in to the ssh server.""")
280 280
281 281 hb_port = Int(0, config=True,
282 282 help="set the heartbeat port [default: random]")
283 283 shell_port = Int(0, config=True,
284 284 help="set the shell (XREP) port [default: random]")
285 285 iopub_port = Int(0, config=True,
286 286 help="set the iopub (PUB) port [default: random]")
287 287 stdin_port = Int(0, config=True,
288 288 help="set the stdin (XREQ) port [default: random]")
289 289
290 290 existing = CBool(False, config=True,
291 291 help="Whether to connect to an already running Kernel.")
292 292
293 293 stylesheet = Unicode('', config=True,
294 294 help="path to a custom CSS stylesheet")
295 295
296 296 pure = CBool(False, config=True,
297 297 help="Use a pure Python kernel instead of an IPython kernel.")
298 298 plain = CBool(False, config=True,
299 299 help="Use a plaintext widget instead of rich text (plain can't print/save).")
300 300
301 301 def _pure_changed(self, name, old, new):
302 302 kind = 'plain' if self.plain else 'rich'
303 303 self.config.ConsoleWidget.kind = kind
304 304 if self.pure:
305 305 self.widget_factory = FrontendWidget
306 306 elif self.plain:
307 307 self.widget_factory = IPythonWidget
308 308 else:
309 309 self.widget_factory = RichIPythonWidget
310 310
311 311 _plain_changed = _pure_changed
312 312
313 313 confirm_exit = CBool(True, config=True,
314 314 help="""
315 315 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
316 316 to force a direct exit without any confirmation.""",
317 317 )
318 318
319 319 # the factory for creating a widget
320 320 widget_factory = Any(RichIPythonWidget)
321 321
322 322 def parse_command_line(self, argv=None):
323 323 super(IPythonQtConsoleApp, self).parse_command_line(argv)
324 324 if argv is None:
325 325 argv = sys.argv[1:]
326 326
327 327 self.kernel_argv = list(argv) # copy
328 328 # kernel should inherit default config file from frontend
329 329 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
330 330 # scrub frontend-specific flags
331 331 for a in argv:
332 332
333 333 if a.startswith('-'):
334 334 key = a.lstrip('-').split('=')[0]
335 335 if key in qt_flags:
336 336 self.kernel_argv.remove(a)
337 337
338 338 def init_ssh(self):
339 339 """set up ssh tunnels, if needed."""
340 340 if not self.sshserver and not self.sshkey:
341 341 return
342 342
343 343 if self.sshkey and not self.sshserver:
344 344 self.sshserver = self.ip
345 345 self.ip=LOCALHOST
346 346
347 347 lports = select_random_ports(4)
348 348 rports = self.shell_port, self.iopub_port, self.stdin_port, self.hb_port
349 349 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = lports
350 350
351 351 remote_ip = self.ip
352 352 self.ip = LOCALHOST
353 353 self.log.info("Forwarding connections to %s via %s"%(remote_ip, self.sshserver))
354 354
355 355 if tunnel.try_passwordless_ssh(self.sshserver, self.sshkey):
356 356 password=False
357 357 else:
358 358 password = getpass("SSH Password for %s: "%self.sshserver)
359 359
360 360 for lp,rp in zip(lports, rports):
361 361 tunnel.ssh_tunnel(lp, rp, self.sshserver, remote_ip, self.sshkey, password)
362 362
363 363 self.log.critical("To connect another client to this tunnel, use:")
364 364 self.log.critical(
365 365 "--existing --shell={0} --iopub={1} --stdin={2} --hb={3}".format(
366 366 self.shell_port, self.iopub_port, self.stdin_port,
367 367 self.hb_port))
368 368
369 369 def init_kernel_manager(self):
370 370 # Don't let Qt or ZMQ swallow KeyboardInterupts.
371 371 signal.signal(signal.SIGINT, signal.SIG_DFL)
372 372
373 373 # Create a KernelManager and start a kernel.
374 self.kernel_manager = QtKernelManager(
375 shell_address=(self.ip, self.shell_port),
376 sub_address=(self.ip, self.iopub_port),
377 stdin_address=(self.ip, self.stdin_port),
378 hb_address=(self.ip, self.hb_port),
379 config=self.config
374 self.kernel_manager = QtKernelManager(ip=self.ip,
375 shell_port=self.shell_port,
376 sub_port=self.iopub_port,
377 stdin_port=self.stdin_port,
378 hb_port=self.hb_port,
379 config=self.config,
380 380 )
381 381 # start the kernel
382 382 if not self.existing:
383 383 kwargs = dict(ip=self.ip, ipython=not self.pure)
384 384 kwargs['extra_arguments'] = self.kernel_argv
385 385 self.kernel_manager.start_kernel(**kwargs)
386 386 self.kernel_manager.start_channels()
387 387
388 388
389 389 def init_qt_elements(self):
390 390 # Create the widget.
391 391 self.app = QtGui.QApplication([])
392 392 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
393 393 self.widget = self.widget_factory(config=self.config,
394 394 local_kernel=local_kernel)
395 395 self.widget.kernel_manager = self.kernel_manager
396 396 self.window = MainWindow(self.app, self.widget, self.existing,
397 397 may_close=local_kernel,
398 398 confirm_exit=self.confirm_exit)
399 399 self.window.setWindowTitle('Python' if self.pure else 'IPython')
400 400
401 401 def init_colors(self):
402 402 """Configure the coloring of the widget"""
403 403 # Note: This will be dramatically simplified when colors
404 404 # are removed from the backend.
405 405
406 406 if self.pure:
407 407 # only IPythonWidget supports styling
408 408 return
409 409
410 410 # parse the colors arg down to current known labels
411 411 try:
412 412 colors = self.config.ZMQInteractiveShell.colors
413 413 except AttributeError:
414 414 colors = None
415 415 try:
416 416 style = self.config.IPythonWidget.colors
417 417 except AttributeError:
418 418 style = None
419 419
420 420 # find the value for colors:
421 421 if colors:
422 422 colors=colors.lower()
423 423 if colors in ('lightbg', 'light'):
424 424 colors='lightbg'
425 425 elif colors in ('dark', 'linux'):
426 426 colors='linux'
427 427 else:
428 428 colors='nocolor'
429 429 elif style:
430 430 if style=='bw':
431 431 colors='nocolor'
432 432 elif styles.dark_style(style):
433 433 colors='linux'
434 434 else:
435 435 colors='lightbg'
436 436 else:
437 437 colors=None
438 438
439 439 # Configure the style.
440 440 widget = self.widget
441 441 if style:
442 442 widget.style_sheet = styles.sheet_from_template(style, colors)
443 443 widget.syntax_style = style
444 444 widget._syntax_style_changed()
445 445 widget._style_sheet_changed()
446 446 elif colors:
447 447 # use a default style
448 448 widget.set_default_style(colors=colors)
449 449 else:
450 450 # this is redundant for now, but allows the widget's
451 451 # defaults to change
452 452 widget.set_default_style()
453 453
454 454 if self.stylesheet:
455 455 # we got an expicit stylesheet
456 456 if os.path.isfile(self.stylesheet):
457 457 with open(self.stylesheet) as f:
458 458 sheet = f.read()
459 459 widget.style_sheet = sheet
460 460 widget._style_sheet_changed()
461 461 else:
462 462 raise IOError("Stylesheet %r not found."%self.stylesheet)
463 463
464 464 def initialize(self, argv=None):
465 465 super(IPythonQtConsoleApp, self).initialize(argv)
466 466 self.init_ssh()
467 467 self.init_kernel_manager()
468 468 self.init_qt_elements()
469 469 self.init_colors()
470 470 self.init_window_shortcut()
471 471
472 472 def init_window_shortcut(self):
473 473 fullScreenAction = QtGui.QAction('Toggle Full Screen', self.window)
474 474 fullScreenAction.setShortcut('Ctrl+Meta+Space')
475 475 fullScreenAction.triggered.connect(self.toggleFullScreen)
476 476 self.window.addAction(fullScreenAction)
477 477
478 478 def toggleFullScreen(self):
479 479 if not self.window.isFullScreen():
480 480 self.window.showFullScreen()
481 481 else:
482 482 self.window.showNormal()
483 483
484 484 def start(self):
485 485
486 486 # draw the window
487 487 self.window.show()
488 488
489 489 # Start the application main loop.
490 490 self.app.exec_()
491 491
492 492 #-----------------------------------------------------------------------------
493 493 # Main entry point
494 494 #-----------------------------------------------------------------------------
495 495
496 496 def main():
497 497 app = IPythonQtConsoleApp()
498 498 app.initialize()
499 499 app.start()
500 500
501 501
502 502 if __name__ == '__main__':
503 503 main()
@@ -1,999 +1,994 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 import atexit
20 19 import errno
21 20 from Queue import Queue, Empty
22 21 from subprocess import Popen
23 22 import signal
24 23 import sys
25 24 from threading import Thread
26 25 import time
27 import logging
28 26
29 27 # System library imports.
30 28 import zmq
31 29 from zmq import POLLIN, POLLOUT, POLLERR
32 30 from zmq.eventloop import ioloop
33 31
34 32 # Local imports.
35 33 from IPython.config.loader import Config
36 from IPython.utils import io
37 34 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
38 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
39 from session import Session, Message
35 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, Unicode, Int
36 from session import Session
40 37
41 38 #-----------------------------------------------------------------------------
42 39 # Constants and exceptions
43 40 #-----------------------------------------------------------------------------
44 41
45 42 class InvalidPortNumber(Exception):
46 43 pass
47 44
48 45 #-----------------------------------------------------------------------------
49 46 # Utility functions
50 47 #-----------------------------------------------------------------------------
51 48
52 49 # some utilities to validate message structure, these might get moved elsewhere
53 50 # if they prove to have more generic utility
54 51
55 52 def validate_string_list(lst):
56 53 """Validate that the input is a list of strings.
57 54
58 55 Raises ValueError if not."""
59 56 if not isinstance(lst, list):
60 57 raise ValueError('input %r must be a list' % lst)
61 58 for x in lst:
62 59 if not isinstance(x, basestring):
63 60 raise ValueError('element %r in list must be a string' % x)
64 61
65 62
66 63 def validate_string_dict(dct):
67 64 """Validate that the input is a dict with string keys and values.
68 65
69 66 Raises ValueError if not."""
70 67 for k,v in dct.iteritems():
71 68 if not isinstance(k, basestring):
72 69 raise ValueError('key %r in dict must be a string' % k)
73 70 if not isinstance(v, basestring):
74 71 raise ValueError('value %r in dict must be a string' % v)
75 72
76 73
77 74 #-----------------------------------------------------------------------------
78 75 # ZMQ Socket Channel classes
79 76 #-----------------------------------------------------------------------------
80 77
81 78 class ZMQSocketChannel(Thread):
82 79 """The base class for the channels that use ZMQ sockets.
83 80 """
84 81 context = None
85 82 session = None
86 83 socket = None
87 84 ioloop = None
88 85 iostate = None
89 86 _address = None
90 87
91 88 def __init__(self, context, session, address):
92 89 """Create a channel
93 90
94 91 Parameters
95 92 ----------
96 93 context : :class:`zmq.Context`
97 94 The ZMQ context to use.
98 95 session : :class:`session.Session`
99 96 The session to use.
100 97 address : tuple
101 98 Standard (ip, port) tuple that the kernel is listening on.
102 99 """
103 100 super(ZMQSocketChannel, self).__init__()
104 101 self.daemon = True
105 102
106 103 self.context = context
107 104 self.session = session
108 105 if address[1] == 0:
109 106 message = 'The port number for a channel cannot be 0.'
110 107 raise InvalidPortNumber(message)
111 108 self._address = address
112 109
113 110 def _run_loop(self):
114 111 """Run my loop, ignoring EINTR events in the poller"""
115 112 while True:
116 113 try:
117 114 self.ioloop.start()
118 115 except zmq.ZMQError as e:
119 116 if e.errno == errno.EINTR:
120 117 continue
121 118 else:
122 119 raise
123 120 else:
124 121 break
125 122
126 123 def stop(self):
127 124 """Stop the channel's activity.
128 125
129 126 This calls :method:`Thread.join` and returns when the thread
130 127 terminates. :class:`RuntimeError` will be raised if
131 128 :method:`self.start` is called again.
132 129 """
133 130 self.join()
134 131
135 132 @property
136 133 def address(self):
137 134 """Get the channel's address as an (ip, port) tuple.
138 135
139 136 By the default, the address is (localhost, 0), where 0 means a random
140 137 port.
141 138 """
142 139 return self._address
143 140
144 141 def add_io_state(self, state):
145 142 """Add IO state to the eventloop.
146 143
147 144 Parameters
148 145 ----------
149 146 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
150 147 The IO state flag to set.
151 148
152 149 This is thread safe as it uses the thread safe IOLoop.add_callback.
153 150 """
154 151 def add_io_state_callback():
155 152 if not self.iostate & state:
156 153 self.iostate = self.iostate | state
157 154 self.ioloop.update_handler(self.socket, self.iostate)
158 155 self.ioloop.add_callback(add_io_state_callback)
159 156
160 157 def drop_io_state(self, state):
161 158 """Drop IO state from the eventloop.
162 159
163 160 Parameters
164 161 ----------
165 162 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
166 163 The IO state flag to set.
167 164
168 165 This is thread safe as it uses the thread safe IOLoop.add_callback.
169 166 """
170 167 def drop_io_state_callback():
171 168 if self.iostate & state:
172 169 self.iostate = self.iostate & (~state)
173 170 self.ioloop.update_handler(self.socket, self.iostate)
174 171 self.ioloop.add_callback(drop_io_state_callback)
175 172
176 173
177 174 class ShellSocketChannel(ZMQSocketChannel):
178 175 """The XREQ channel for issues request/replies to the kernel.
179 176 """
180 177
181 178 command_queue = None
182 179 # flag for whether execute requests should be allowed to call raw_input:
183 180 allow_stdin = True
184 181
185 182 def __init__(self, context, session, address):
186 183 super(ShellSocketChannel, self).__init__(context, session, address)
187 184 self.command_queue = Queue()
188 185 self.ioloop = ioloop.IOLoop()
189 186
190 187 def run(self):
191 188 """The thread's main activity. Call start() instead."""
192 189 self.socket = self.context.socket(zmq.DEALER)
193 190 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
194 191 self.socket.connect('tcp://%s:%i' % self.address)
195 192 self.iostate = POLLERR|POLLIN
196 193 self.ioloop.add_handler(self.socket, self._handle_events,
197 194 self.iostate)
198 195 self._run_loop()
199 196
200 197 def stop(self):
201 198 self.ioloop.stop()
202 199 super(ShellSocketChannel, self).stop()
203 200
204 201 def call_handlers(self, msg):
205 202 """This method is called in the ioloop thread when a message arrives.
206 203
207 204 Subclasses should override this method to handle incoming messages.
208 205 It is important to remember that this method is called in the thread
209 206 so that some logic must be done to ensure that the application leve
210 207 handlers are called in the application thread.
211 208 """
212 209 raise NotImplementedError('call_handlers must be defined in a subclass.')
213 210
214 211 def execute(self, code, silent=False,
215 212 user_variables=None, user_expressions=None, allow_stdin=None):
216 213 """Execute code in the kernel.
217 214
218 215 Parameters
219 216 ----------
220 217 code : str
221 218 A string of Python code.
222 219
223 220 silent : bool, optional (default False)
224 221 If set, the kernel will execute the code as quietly possible.
225 222
226 223 user_variables : list, optional
227 224 A list of variable names to pull from the user's namespace. They
228 225 will come back as a dict with these names as keys and their
229 226 :func:`repr` as values.
230 227
231 228 user_expressions : dict, optional
232 229 A dict with string keys and to pull from the user's
233 230 namespace. They will come back as a dict with these names as keys
234 231 and their :func:`repr` as values.
235 232
236 233 allow_stdin : bool, optional
237 234 Flag for
238 235 A dict with string keys and to pull from the user's
239 236 namespace. They will come back as a dict with these names as keys
240 237 and their :func:`repr` as values.
241 238
242 239 Returns
243 240 -------
244 241 The msg_id of the message sent.
245 242 """
246 243 if user_variables is None:
247 244 user_variables = []
248 245 if user_expressions is None:
249 246 user_expressions = {}
250 247 if allow_stdin is None:
251 248 allow_stdin = self.allow_stdin
252 249
253 250
254 251 # Don't waste network traffic if inputs are invalid
255 252 if not isinstance(code, basestring):
256 253 raise ValueError('code %r must be a string' % code)
257 254 validate_string_list(user_variables)
258 255 validate_string_dict(user_expressions)
259 256
260 257 # Create class for content/msg creation. Related to, but possibly
261 258 # not in Session.
262 259 content = dict(code=code, silent=silent,
263 260 user_variables=user_variables,
264 261 user_expressions=user_expressions,
265 262 allow_stdin=allow_stdin,
266 263 )
267 264 msg = self.session.msg('execute_request', content)
268 265 self._queue_request(msg)
269 266 return msg['header']['msg_id']
270 267
271 268 def complete(self, text, line, cursor_pos, block=None):
272 269 """Tab complete text in the kernel's namespace.
273 270
274 271 Parameters
275 272 ----------
276 273 text : str
277 274 The text to complete.
278 275 line : str
279 276 The full line of text that is the surrounding context for the
280 277 text to complete.
281 278 cursor_pos : int
282 279 The position of the cursor in the line where the completion was
283 280 requested.
284 281 block : str, optional
285 282 The full block of code in which the completion is being requested.
286 283
287 284 Returns
288 285 -------
289 286 The msg_id of the message sent.
290 287 """
291 288 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
292 289 msg = self.session.msg('complete_request', content)
293 290 self._queue_request(msg)
294 291 return msg['header']['msg_id']
295 292
296 293 def object_info(self, oname):
297 294 """Get metadata information about an object.
298 295
299 296 Parameters
300 297 ----------
301 298 oname : str
302 299 A string specifying the object name.
303 300
304 301 Returns
305 302 -------
306 303 The msg_id of the message sent.
307 304 """
308 305 content = dict(oname=oname)
309 306 msg = self.session.msg('object_info_request', content)
310 307 self._queue_request(msg)
311 308 return msg['header']['msg_id']
312 309
313 310 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
314 311 """Get entries from the history list.
315 312
316 313 Parameters
317 314 ----------
318 315 raw : bool
319 316 If True, return the raw input.
320 317 output : bool
321 318 If True, then return the output as well.
322 319 hist_access_type : str
323 320 'range' (fill in session, start and stop params), 'tail' (fill in n)
324 321 or 'search' (fill in pattern param).
325 322
326 323 session : int
327 324 For a range request, the session from which to get lines. Session
328 325 numbers are positive integers; negative ones count back from the
329 326 current session.
330 327 start : int
331 328 The first line number of a history range.
332 329 stop : int
333 330 The final (excluded) line number of a history range.
334 331
335 332 n : int
336 333 The number of lines of history to get for a tail request.
337 334
338 335 pattern : str
339 336 The glob-syntax pattern for a search request.
340 337
341 338 Returns
342 339 -------
343 340 The msg_id of the message sent.
344 341 """
345 342 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
346 343 **kwargs)
347 344 msg = self.session.msg('history_request', content)
348 345 self._queue_request(msg)
349 346 return msg['header']['msg_id']
350 347
351 348 def shutdown(self, restart=False):
352 349 """Request an immediate kernel shutdown.
353 350
354 351 Upon receipt of the (empty) reply, client code can safely assume that
355 352 the kernel has shut down and it's safe to forcefully terminate it if
356 353 it's still alive.
357 354
358 355 The kernel will send the reply via a function registered with Python's
359 356 atexit module, ensuring it's truly done as the kernel is done with all
360 357 normal operation.
361 358 """
362 359 # Send quit message to kernel. Once we implement kernel-side setattr,
363 360 # this should probably be done that way, but for now this will do.
364 361 msg = self.session.msg('shutdown_request', {'restart':restart})
365 362 self._queue_request(msg)
366 363 return msg['header']['msg_id']
367 364
368 365 def _handle_events(self, socket, events):
369 366 if events & POLLERR:
370 367 self._handle_err()
371 368 if events & POLLOUT:
372 369 self._handle_send()
373 370 if events & POLLIN:
374 371 self._handle_recv()
375 372
376 373 def _handle_recv(self):
377 374 ident,msg = self.session.recv(self.socket, 0)
378 375 self.call_handlers(msg)
379 376
380 377 def _handle_send(self):
381 378 try:
382 379 msg = self.command_queue.get(False)
383 380 except Empty:
384 381 pass
385 382 else:
386 383 self.session.send(self.socket,msg)
387 384 if self.command_queue.empty():
388 385 self.drop_io_state(POLLOUT)
389 386
390 387 def _handle_err(self):
391 388 # We don't want to let this go silently, so eventually we should log.
392 389 raise zmq.ZMQError()
393 390
394 391 def _queue_request(self, msg):
395 392 self.command_queue.put(msg)
396 393 self.add_io_state(POLLOUT)
397 394
398 395
399 396 class SubSocketChannel(ZMQSocketChannel):
400 397 """The SUB channel which listens for messages that the kernel publishes.
401 398 """
402 399
403 400 def __init__(self, context, session, address):
404 401 super(SubSocketChannel, self).__init__(context, session, address)
405 402 self.ioloop = ioloop.IOLoop()
406 403
407 404 def run(self):
408 405 """The thread's main activity. Call start() instead."""
409 406 self.socket = self.context.socket(zmq.SUB)
410 407 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
411 408 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
412 409 self.socket.connect('tcp://%s:%i' % self.address)
413 410 self.iostate = POLLIN|POLLERR
414 411 self.ioloop.add_handler(self.socket, self._handle_events,
415 412 self.iostate)
416 413 self._run_loop()
417 414
418 415 def stop(self):
419 416 self.ioloop.stop()
420 417 super(SubSocketChannel, self).stop()
421 418
422 419 def call_handlers(self, msg):
423 420 """This method is called in the ioloop thread when a message arrives.
424 421
425 422 Subclasses should override this method to handle incoming messages.
426 423 It is important to remember that this method is called in the thread
427 424 so that some logic must be done to ensure that the application leve
428 425 handlers are called in the application thread.
429 426 """
430 427 raise NotImplementedError('call_handlers must be defined in a subclass.')
431 428
432 429 def flush(self, timeout=1.0):
433 430 """Immediately processes all pending messages on the SUB channel.
434 431
435 432 Callers should use this method to ensure that :method:`call_handlers`
436 433 has been called for all messages that have been received on the
437 434 0MQ SUB socket of this channel.
438 435
439 436 This method is thread safe.
440 437
441 438 Parameters
442 439 ----------
443 440 timeout : float, optional
444 441 The maximum amount of time to spend flushing, in seconds. The
445 442 default is one second.
446 443 """
447 444 # We do the IOLoop callback process twice to ensure that the IOLoop
448 445 # gets to perform at least one full poll.
449 446 stop_time = time.time() + timeout
450 447 for i in xrange(2):
451 448 self._flushed = False
452 449 self.ioloop.add_callback(self._flush)
453 450 while not self._flushed and time.time() < stop_time:
454 451 time.sleep(0.01)
455 452
456 453 def _handle_events(self, socket, events):
457 454 # Turn on and off POLLOUT depending on if we have made a request
458 455 if events & POLLERR:
459 456 self._handle_err()
460 457 if events & POLLIN:
461 458 self._handle_recv()
462 459
463 460 def _handle_err(self):
464 461 # We don't want to let this go silently, so eventually we should log.
465 462 raise zmq.ZMQError()
466 463
467 464 def _handle_recv(self):
468 465 # Get all of the messages we can
469 466 while True:
470 467 try:
471 468 ident,msg = self.session.recv(self.socket)
472 469 except zmq.ZMQError:
473 470 # Check the errno?
474 471 # Will this trigger POLLERR?
475 472 break
476 473 else:
477 474 if msg is None:
478 475 break
479 476 self.call_handlers(msg)
480 477
481 478 def _flush(self):
482 479 """Callback for :method:`self.flush`."""
483 480 self._flushed = True
484 481
485 482
486 483 class StdInSocketChannel(ZMQSocketChannel):
487 484 """A reply channel to handle raw_input requests that the kernel makes."""
488 485
489 486 msg_queue = None
490 487
491 488 def __init__(self, context, session, address):
492 489 super(StdInSocketChannel, self).__init__(context, session, address)
493 490 self.ioloop = ioloop.IOLoop()
494 491 self.msg_queue = Queue()
495 492
496 493 def run(self):
497 494 """The thread's main activity. Call start() instead."""
498 495 self.socket = self.context.socket(zmq.DEALER)
499 496 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
500 497 self.socket.connect('tcp://%s:%i' % self.address)
501 498 self.iostate = POLLERR|POLLIN
502 499 self.ioloop.add_handler(self.socket, self._handle_events,
503 500 self.iostate)
504 501 self._run_loop()
505 502
506 503 def stop(self):
507 504 self.ioloop.stop()
508 505 super(StdInSocketChannel, self).stop()
509 506
510 507 def call_handlers(self, msg):
511 508 """This method is called in the ioloop thread when a message arrives.
512 509
513 510 Subclasses should override this method to handle incoming messages.
514 511 It is important to remember that this method is called in the thread
515 512 so that some logic must be done to ensure that the application leve
516 513 handlers are called in the application thread.
517 514 """
518 515 raise NotImplementedError('call_handlers must be defined in a subclass.')
519 516
520 517 def input(self, string):
521 518 """Send a string of raw input to the kernel."""
522 519 content = dict(value=string)
523 520 msg = self.session.msg('input_reply', content)
524 521 self._queue_reply(msg)
525 522
526 523 def _handle_events(self, socket, events):
527 524 if events & POLLERR:
528 525 self._handle_err()
529 526 if events & POLLOUT:
530 527 self._handle_send()
531 528 if events & POLLIN:
532 529 self._handle_recv()
533 530
534 531 def _handle_recv(self):
535 532 ident,msg = self.session.recv(self.socket, 0)
536 533 self.call_handlers(msg)
537 534
538 535 def _handle_send(self):
539 536 try:
540 537 msg = self.msg_queue.get(False)
541 538 except Empty:
542 539 pass
543 540 else:
544 541 self.session.send(self.socket,msg)
545 542 if self.msg_queue.empty():
546 543 self.drop_io_state(POLLOUT)
547 544
548 545 def _handle_err(self):
549 546 # We don't want to let this go silently, so eventually we should log.
550 547 raise zmq.ZMQError()
551 548
552 549 def _queue_reply(self, msg):
553 550 self.msg_queue.put(msg)
554 551 self.add_io_state(POLLOUT)
555 552
556 553
557 554 class HBSocketChannel(ZMQSocketChannel):
558 555 """The heartbeat channel which monitors the kernel heartbeat.
559 556
560 557 Note that the heartbeat channel is paused by default. As long as you start
561 558 this channel, the kernel manager will ensure that it is paused and un-paused
562 559 as appropriate.
563 560 """
564 561
565 562 time_to_dead = 3.0
566 563 socket = None
567 564 poller = None
568 565 _running = None
569 566 _pause = None
570 567
571 568 def __init__(self, context, session, address):
572 569 super(HBSocketChannel, self).__init__(context, session, address)
573 570 self._running = False
574 571 self._pause = True
575 572
576 573 def _create_socket(self):
577 574 self.socket = self.context.socket(zmq.REQ)
578 575 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
579 576 self.socket.connect('tcp://%s:%i' % self.address)
580 577 self.poller = zmq.Poller()
581 578 self.poller.register(self.socket, zmq.POLLIN)
582 579
583 580 def run(self):
584 581 """The thread's main activity. Call start() instead."""
585 582 self._create_socket()
586 583 self._running = True
587 584 while self._running:
588 585 if self._pause:
589 586 time.sleep(self.time_to_dead)
590 587 else:
591 588 since_last_heartbeat = 0.0
592 589 request_time = time.time()
593 590 try:
594 591 #io.rprint('Ping from HB channel') # dbg
595 592 self.socket.send(b'ping')
596 593 except zmq.ZMQError, e:
597 594 #io.rprint('*** HB Error:', e) # dbg
598 595 if e.errno == zmq.EFSM:
599 596 #io.rprint('sleep...', self.time_to_dead) # dbg
600 597 time.sleep(self.time_to_dead)
601 598 self._create_socket()
602 599 else:
603 600 raise
604 601 else:
605 602 while True:
606 603 try:
607 604 self.socket.recv(zmq.NOBLOCK)
608 605 except zmq.ZMQError, e:
609 606 #io.rprint('*** HB Error 2:', e) # dbg
610 607 if e.errno == zmq.EAGAIN:
611 608 before_poll = time.time()
612 609 until_dead = self.time_to_dead - (before_poll -
613 610 request_time)
614 611
615 612 # When the return value of poll() is an empty
616 613 # list, that is when things have gone wrong
617 614 # (zeromq bug). As long as it is not an empty
618 615 # list, poll is working correctly even if it
619 616 # returns quickly. Note: poll timeout is in
620 617 # milliseconds.
621 618 if until_dead > 0.0:
622 619 while True:
623 620 try:
624 621 self.poller.poll(1000 * until_dead)
625 622 except zmq.ZMQError as e:
626 623 if e.errno == errno.EINTR:
627 624 continue
628 625 else:
629 626 raise
630 627 else:
631 628 break
632 629
633 630 since_last_heartbeat = time.time()-request_time
634 631 if since_last_heartbeat > self.time_to_dead:
635 632 self.call_handlers(since_last_heartbeat)
636 633 break
637 634 else:
638 635 # FIXME: We should probably log this instead.
639 636 raise
640 637 else:
641 638 until_dead = self.time_to_dead - (time.time() -
642 639 request_time)
643 640 if until_dead > 0.0:
644 641 #io.rprint('sleep...', self.time_to_dead) # dbg
645 642 time.sleep(until_dead)
646 643 break
647 644
648 645 def pause(self):
649 646 """Pause the heartbeat."""
650 647 self._pause = True
651 648
652 649 def unpause(self):
653 650 """Unpause the heartbeat."""
654 651 self._pause = False
655 652
656 653 def is_beating(self):
657 654 """Is the heartbeat running and not paused."""
658 655 if self.is_alive() and not self._pause:
659 656 return True
660 657 else:
661 658 return False
662 659
663 660 def stop(self):
664 661 self._running = False
665 662 super(HBSocketChannel, self).stop()
666 663
667 664 def call_handlers(self, since_last_heartbeat):
668 665 """This method is called in the ioloop thread when a message arrives.
669 666
670 667 Subclasses should override this method to handle incoming messages.
671 668 It is important to remember that this method is called in the thread
672 669 so that some logic must be done to ensure that the application leve
673 670 handlers are called in the application thread.
674 671 """
675 672 raise NotImplementedError('call_handlers must be defined in a subclass.')
676 673
677 674
678 675 #-----------------------------------------------------------------------------
679 676 # Main kernel manager class
680 677 #-----------------------------------------------------------------------------
681 678
682 679 class KernelManager(HasTraits):
683 680 """ Manages a kernel for a frontend.
684 681
685 682 The SUB channel is for the frontend to receive messages published by the
686 683 kernel.
687 684
688 685 The REQ channel is for the frontend to make requests of the kernel.
689 686
690 687 The REP channel is for the kernel to request stdin (raw_input) from the
691 688 frontend.
692 689 """
693 690 # config object for passing to child configurables
694 691 config = Instance(Config)
695 692
696 693 # The PyZMQ Context to use for communication with the kernel.
697 694 context = Instance(zmq.Context)
698 695 def _context_default(self):
699 696 return zmq.Context.instance()
700 697
701 698 # The Session to use for communication with the kernel.
702 699 session = Instance(Session)
703 700
704 701 # The kernel process with which the KernelManager is communicating.
705 702 kernel = Instance(Popen)
706 703
707 704 # The addresses for the communication channels.
708 shell_address = TCPAddress((LOCALHOST, 0))
709 sub_address = TCPAddress((LOCALHOST, 0))
710 stdin_address = TCPAddress((LOCALHOST, 0))
711 hb_address = TCPAddress((LOCALHOST, 0))
705 ip = Unicode(LOCALHOST)
706 shell_port = Int(0)
707 sub_port = Int(0)
708 stdin_port = Int(0)
709 hb_port = Int(0)
712 710
713 711 # The classes to use for the various channels.
714 712 shell_channel_class = Type(ShellSocketChannel)
715 713 sub_channel_class = Type(SubSocketChannel)
716 714 stdin_channel_class = Type(StdInSocketChannel)
717 715 hb_channel_class = Type(HBSocketChannel)
718 716
719 717 # Protected traits.
720 718 _launch_args = Any
721 719 _shell_channel = Any
722 720 _sub_channel = Any
723 721 _stdin_channel = Any
724 722 _hb_channel = Any
725 723
726 724 def __init__(self, **kwargs):
727 725 super(KernelManager, self).__init__(**kwargs)
728 726 if self.session is None:
729 727 self.session = Session(config=self.config)
730 728 # Uncomment this to try closing the context.
731 729 # atexit.register(self.context.term)
732 730
733 731 #--------------------------------------------------------------------------
734 732 # Channel management methods:
735 733 #--------------------------------------------------------------------------
736 734
737 735 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
738 736 """Starts the channels for this kernel.
739 737
740 738 This will create the channels if they do not exist and then start
741 739 them. If port numbers of 0 are being used (random ports) then you
742 740 must first call :method:`start_kernel`. If the channels have been
743 741 stopped and you call this, :class:`RuntimeError` will be raised.
744 742 """
745 743 if shell:
746 744 self.shell_channel.start()
747 745 if sub:
748 746 self.sub_channel.start()
749 747 if stdin:
750 748 self.stdin_channel.start()
751 749 self.shell_channel.allow_stdin = True
752 750 else:
753 751 self.shell_channel.allow_stdin = False
754 752 if hb:
755 753 self.hb_channel.start()
756 754
757 755 def stop_channels(self):
758 756 """Stops all the running channels for this kernel.
759 757 """
760 758 if self.shell_channel.is_alive():
761 759 self.shell_channel.stop()
762 760 if self.sub_channel.is_alive():
763 761 self.sub_channel.stop()
764 762 if self.stdin_channel.is_alive():
765 763 self.stdin_channel.stop()
766 764 if self.hb_channel.is_alive():
767 765 self.hb_channel.stop()
768 766
769 767 @property
770 768 def channels_running(self):
771 769 """Are any of the channels created and running?"""
772 770 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
773 771 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
774 772
775 773 #--------------------------------------------------------------------------
776 774 # Kernel process management methods:
777 775 #--------------------------------------------------------------------------
778 776
779 777 def start_kernel(self, **kw):
780 778 """Starts a kernel process and configures the manager to use it.
781 779
782 780 If random ports (port=0) are being used, this method must be called
783 781 before the channels are created.
784 782
785 783 Parameters:
786 784 -----------
787 785 ipython : bool, optional (default True)
788 786 Whether to use an IPython kernel instead of a plain Python kernel.
789 787
790 788 launcher : callable, optional (default None)
791 789 A custom function for launching the kernel process (generally a
792 790 wrapper around ``entry_point.base_launch_kernel``). In most cases,
793 791 it should not be necessary to use this parameter.
794 792
795 793 **kw : optional
796 794 See respective options for IPython and Python kernels.
797 795 """
798 shell, sub, stdin, hb = self.shell_address, self.sub_address, \
799 self.stdin_address, self.hb_address
800 if shell[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
801 stdin[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
796 if self.ip not in LOCAL_IPS:
802 797 raise RuntimeError("Can only launch a kernel on a local interface. "
803 798 "Make sure that the '*_address' attributes are "
804 799 "configured properly. "
805 800 "Currently valid addresses are: %s"%LOCAL_IPS
806 801 )
807 802
808 803 self._launch_args = kw.copy()
809 804 launch_kernel = kw.pop('launcher', None)
810 805 if launch_kernel is None:
811 806 if kw.pop('ipython', True):
812 807 from ipkernel import launch_kernel
813 808 else:
814 809 from pykernel import launch_kernel
815 self.kernel, xrep, pub, req, _hb = launch_kernel(
816 shell_port=shell[1], iopub_port=sub[1],
817 stdin_port=stdin[1], hb_port=hb[1], **kw)
818 self.shell_address = (shell[0], xrep)
819 self.sub_address = (sub[0], pub)
820 self.stdin_address = (stdin[0], req)
821 self.hb_address = (hb[0], _hb)
810 self.kernel, shell, sub, stdin, hb = launch_kernel(
811 shell_port=self.shell_port, iopub_port=self.sub_port,
812 stdin_port=self.stdin_port, hb_port=self.hb_port, **kw)
813 self.shell_port = shell
814 self.sub_port = sub
815 self.stdin_port = stdin
816 self.hb_port = hb
822 817
823 818 def shutdown_kernel(self, restart=False):
824 819 """ Attempts to the stop the kernel process cleanly. If the kernel
825 820 cannot be stopped, it is killed, if possible.
826 821 """
827 822 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
828 823 if sys.platform == 'win32':
829 824 self.kill_kernel()
830 825 return
831 826
832 827 # Pause the heart beat channel if it exists.
833 828 if self._hb_channel is not None:
834 829 self._hb_channel.pause()
835 830
836 831 # Don't send any additional kernel kill messages immediately, to give
837 832 # the kernel a chance to properly execute shutdown actions. Wait for at
838 833 # most 1s, checking every 0.1s.
839 834 self.shell_channel.shutdown(restart=restart)
840 835 for i in range(10):
841 836 if self.is_alive:
842 837 time.sleep(0.1)
843 838 else:
844 839 break
845 840 else:
846 841 # OK, we've waited long enough.
847 842 if self.has_kernel:
848 843 self.kill_kernel()
849 844
850 845 def restart_kernel(self, now=False, **kw):
851 846 """Restarts a kernel with the arguments that were used to launch it.
852 847
853 848 If the old kernel was launched with random ports, the same ports will be
854 849 used for the new kernel.
855 850
856 851 Parameters
857 852 ----------
858 853 now : bool, optional
859 854 If True, the kernel is forcefully restarted *immediately*, without
860 855 having a chance to do any cleanup action. Otherwise the kernel is
861 856 given 1s to clean up before a forceful restart is issued.
862 857
863 858 In all cases the kernel is restarted, the only difference is whether
864 859 it is given a chance to perform a clean shutdown or not.
865 860
866 861 **kw : optional
867 862 Any options specified here will replace those used to launch the
868 863 kernel.
869 864 """
870 865 if self._launch_args is None:
871 866 raise RuntimeError("Cannot restart the kernel. "
872 867 "No previous call to 'start_kernel'.")
873 868 else:
874 869 # Stop currently running kernel.
875 870 if self.has_kernel:
876 871 if now:
877 872 self.kill_kernel()
878 873 else:
879 874 self.shutdown_kernel(restart=True)
880 875
881 876 # Start new kernel.
882 877 self._launch_args.update(kw)
883 878 self.start_kernel(**self._launch_args)
884 879
885 880 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
886 881 # unless there is some delay here.
887 882 if sys.platform == 'win32':
888 883 time.sleep(0.2)
889 884
890 885 @property
891 886 def has_kernel(self):
892 887 """Returns whether a kernel process has been specified for the kernel
893 888 manager.
894 889 """
895 890 return self.kernel is not None
896 891
897 892 def kill_kernel(self):
898 893 """ Kill the running kernel. """
899 894 if self.has_kernel:
900 895 # Pause the heart beat channel if it exists.
901 896 if self._hb_channel is not None:
902 897 self._hb_channel.pause()
903 898
904 899 # Attempt to kill the kernel.
905 900 try:
906 901 self.kernel.kill()
907 902 except OSError, e:
908 903 # In Windows, we will get an Access Denied error if the process
909 904 # has already terminated. Ignore it.
910 905 if sys.platform == 'win32':
911 906 if e.winerror != 5:
912 907 raise
913 908 # On Unix, we may get an ESRCH error if the process has already
914 909 # terminated. Ignore it.
915 910 else:
916 911 from errno import ESRCH
917 912 if e.errno != ESRCH:
918 913 raise
919 914 self.kernel = None
920 915 else:
921 916 raise RuntimeError("Cannot kill kernel. No kernel is running!")
922 917
923 918 def interrupt_kernel(self):
924 919 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
925 920 well supported on all platforms.
926 921 """
927 922 if self.has_kernel:
928 923 if sys.platform == 'win32':
929 924 from parentpoller import ParentPollerWindows as Poller
930 925 Poller.send_interrupt(self.kernel.win32_interrupt_event)
931 926 else:
932 927 self.kernel.send_signal(signal.SIGINT)
933 928 else:
934 929 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
935 930
936 931 def signal_kernel(self, signum):
937 932 """ Sends a signal to the kernel. Note that since only SIGTERM is
938 933 supported on Windows, this function is only useful on Unix systems.
939 934 """
940 935 if self.has_kernel:
941 936 self.kernel.send_signal(signum)
942 937 else:
943 938 raise RuntimeError("Cannot signal kernel. No kernel is running!")
944 939
945 940 @property
946 941 def is_alive(self):
947 942 """Is the kernel process still running?"""
948 943 # FIXME: not using a heartbeat means this method is broken for any
949 944 # remote kernel, it's only capable of handling local kernels.
950 945 if self.has_kernel:
951 946 if self.kernel.poll() is None:
952 947 return True
953 948 else:
954 949 return False
955 950 else:
956 951 # We didn't start the kernel with this KernelManager so we don't
957 952 # know if it is running. We should use a heartbeat for this case.
958 953 return True
959 954
960 955 #--------------------------------------------------------------------------
961 956 # Channels used for communication with the kernel:
962 957 #--------------------------------------------------------------------------
963 958
964 959 @property
965 960 def shell_channel(self):
966 961 """Get the REQ socket channel object to make requests of the kernel."""
967 962 if self._shell_channel is None:
968 963 self._shell_channel = self.shell_channel_class(self.context,
969 964 self.session,
970 self.shell_address)
965 (self.ip, self.shell_port))
971 966 return self._shell_channel
972 967
973 968 @property
974 969 def sub_channel(self):
975 970 """Get the SUB socket channel object."""
976 971 if self._sub_channel is None:
977 972 self._sub_channel = self.sub_channel_class(self.context,
978 973 self.session,
979 self.sub_address)
974 (self.ip, self.sub_port))
980 975 return self._sub_channel
981 976
982 977 @property
983 978 def stdin_channel(self):
984 979 """Get the REP socket channel object to handle stdin (raw_input)."""
985 980 if self._stdin_channel is None:
986 981 self._stdin_channel = self.stdin_channel_class(self.context,
987 982 self.session,
988 self.stdin_address)
983 (self.ip, self.stdin_port))
989 984 return self._stdin_channel
990 985
991 986 @property
992 987 def hb_channel(self):
993 988 """Get the heartbeat socket channel object to check that the
994 989 kernel is alive."""
995 990 if self._hb_channel is None:
996 991 self._hb_channel = self.hb_channel_class(self.context,
997 992 self.session,
998 self.hb_address)
993 (self.ip, self.hb_port))
999 994 return self._hb_channel
General Comments 0
You need to be logged in to leave comments. Login now