##// END OF EJS Templates
finish plumbing config to Session objects...
MinRK -
Show More
@@ -1,402 +1,404 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2 """
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Imports
6 6 #-----------------------------------------------------------------------------
7 7
8 8 # stdlib imports
9 9 import os
10 10 import signal
11 11 import sys
12 12
13 13 # System library imports
14 14 from IPython.external.qt import QtGui
15 15 from pygments.styles import get_all_styles
16 16
17 17 # Local imports
18 18 from IPython.config.application import boolean_flag
19 19 from IPython.core.newapplication import ProfileDir, BaseIPythonApplication
20 20 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
21 21 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
22 22 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
23 23 from IPython.frontend.qt.console import styles
24 24 from IPython.frontend.qt.kernelmanager import QtKernelManager
25 25 from IPython.utils.traitlets import (
26 26 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
27 27 )
28 28 from IPython.zmq.ipkernel import (
29 29 flags as ipkernel_flags,
30 30 aliases as ipkernel_aliases,
31 31 IPKernelApp
32 32 )
33 from IPython.zmq.session import Session
33 34 from IPython.zmq.zmqshell import ZMQInteractiveShell
34 35
35 36
36 37 #-----------------------------------------------------------------------------
37 38 # Network Constants
38 39 #-----------------------------------------------------------------------------
39 40
40 41 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
41 42
42 43 #-----------------------------------------------------------------------------
43 44 # Classes
44 45 #-----------------------------------------------------------------------------
45 46
46 47 class MainWindow(QtGui.QMainWindow):
47 48
48 49 #---------------------------------------------------------------------------
49 50 # 'object' interface
50 51 #---------------------------------------------------------------------------
51 52
52 53 def __init__(self, app, frontend, existing=False, may_close=True,
53 54 confirm_exit=True):
54 55 """ Create a MainWindow for the specified FrontendWidget.
55 56
56 57 The app is passed as an argument to allow for different
57 58 closing behavior depending on whether we are the Kernel's parent.
58 59
59 60 If existing is True, then this Console does not own the Kernel.
60 61
61 62 If may_close is True, then this Console is permitted to close the kernel
62 63 """
63 64 super(MainWindow, self).__init__()
64 65 self._app = app
65 66 self._frontend = frontend
66 67 self._existing = existing
67 68 if existing:
68 69 self._may_close = may_close
69 70 else:
70 71 self._may_close = True
71 72 self._frontend.exit_requested.connect(self.close)
72 73 self._confirm_exit = confirm_exit
73 74 self.setCentralWidget(frontend)
74 75
75 76 #---------------------------------------------------------------------------
76 77 # QWidget interface
77 78 #---------------------------------------------------------------------------
78 79
79 80 def closeEvent(self, event):
80 81 """ Close the window and the kernel (if necessary).
81 82
82 83 This will prompt the user if they are finished with the kernel, and if
83 84 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
84 85 it closes without prompt.
85 86 """
86 87 keepkernel = None #Use the prompt by default
87 88 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
88 89 keepkernel = self._frontend._keep_kernel_on_exit
89 90
90 91 kernel_manager = self._frontend.kernel_manager
91 92
92 93 if keepkernel is None and not self._confirm_exit:
93 94 # don't prompt, just terminate the kernel if we own it
94 95 # or leave it alone if we don't
95 96 keepkernel = not self._existing
96 97
97 98 if keepkernel is None: #show prompt
98 99 if kernel_manager and kernel_manager.channels_running:
99 100 title = self.window().windowTitle()
100 101 cancel = QtGui.QMessageBox.Cancel
101 102 okay = QtGui.QMessageBox.Ok
102 103 if self._may_close:
103 104 msg = "You are closing this Console window."
104 105 info = "Would you like to quit the Kernel and all attached Consoles as well?"
105 106 justthis = QtGui.QPushButton("&No, just this Console", self)
106 107 justthis.setShortcut('N')
107 108 closeall = QtGui.QPushButton("&Yes, quit everything", self)
108 109 closeall.setShortcut('Y')
109 110 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
110 111 title, msg)
111 112 box.setInformativeText(info)
112 113 box.addButton(cancel)
113 114 box.addButton(justthis, QtGui.QMessageBox.NoRole)
114 115 box.addButton(closeall, QtGui.QMessageBox.YesRole)
115 116 box.setDefaultButton(closeall)
116 117 box.setEscapeButton(cancel)
117 118 reply = box.exec_()
118 119 if reply == 1: # close All
119 120 kernel_manager.shutdown_kernel()
120 121 #kernel_manager.stop_channels()
121 122 event.accept()
122 123 elif reply == 0: # close Console
123 124 if not self._existing:
124 125 # Have kernel: don't quit, just close the window
125 126 self._app.setQuitOnLastWindowClosed(False)
126 127 self.deleteLater()
127 128 event.accept()
128 129 else:
129 130 event.ignore()
130 131 else:
131 132 reply = QtGui.QMessageBox.question(self, title,
132 133 "Are you sure you want to close this Console?"+
133 134 "\nThe Kernel and other Consoles will remain active.",
134 135 okay|cancel,
135 136 defaultButton=okay
136 137 )
137 138 if reply == okay:
138 139 event.accept()
139 140 else:
140 141 event.ignore()
141 142 elif keepkernel: #close console but leave kernel running (no prompt)
142 143 if kernel_manager and kernel_manager.channels_running:
143 144 if not self._existing:
144 145 # I have the kernel: don't quit, just close the window
145 146 self._app.setQuitOnLastWindowClosed(False)
146 147 event.accept()
147 148 else: #close console and kernel (no prompt)
148 149 if kernel_manager and kernel_manager.channels_running:
149 150 kernel_manager.shutdown_kernel()
150 151 event.accept()
151 152
152 153 #-----------------------------------------------------------------------------
153 154 # Aliases and Flags
154 155 #-----------------------------------------------------------------------------
155 156
156 157 flags = dict(ipkernel_flags)
157 158
158 159 flags.update({
159 160 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
160 161 "Connect to an existing kernel."),
161 162 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
162 163 "Use a pure Python kernel instead of an IPython kernel."),
163 164 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
164 165 "Disable rich text support."),
165 166 })
166 167 flags.update(boolean_flag(
167 168 'gui-completion', 'ConsoleWidget.gui_completion',
168 169 "use a GUI widget for tab completion",
169 170 "use plaintext output for completion"
170 171 ))
171 172 flags.update(boolean_flag(
172 173 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
173 174 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
174 175 to force a direct exit without any confirmation.
175 176 """,
176 177 """Don't prompt the user when exiting. This will terminate the kernel
177 178 if it is owned by the frontend, and leave it alive if it is external.
178 179 """
179 180 ))
180 181 # the flags that are specific to the frontend
181 182 # these must be scrubbed before being passed to the kernel,
182 183 # or it will raise an error on unrecognized flags
183 184 qt_flags = ['existing', 'pure', 'plain', 'gui-completion', 'no-gui-completion',
184 185 'confirm-exit', 'no-confirm-exit']
185 186
186 187 aliases = dict(ipkernel_aliases)
187 188
188 189 aliases.update(dict(
189 190 hb = 'IPythonQtConsoleApp.hb_port',
190 191 shell = 'IPythonQtConsoleApp.shell_port',
191 192 iopub = 'IPythonQtConsoleApp.iopub_port',
192 193 stdin = 'IPythonQtConsoleApp.stdin_port',
193 194 ip = 'IPythonQtConsoleApp.ip',
194 195
195 196 plain = 'IPythonQtConsoleApp.plain',
196 197 pure = 'IPythonQtConsoleApp.pure',
197 198 gui_completion = 'ConsoleWidget.gui_completion',
198 199 style = 'IPythonWidget.syntax_style',
199 200 stylesheet = 'IPythonQtConsoleApp.stylesheet',
200 201 colors = 'ZMQInteractiveShell.colors',
201 202
202 203 editor = 'IPythonWidget.editor',
203 204 pi = 'IPythonWidget.in_prompt',
204 205 po = 'IPythonWidget.out_prompt',
205 206 si = 'IPythonWidget.input_sep',
206 207 so = 'IPythonWidget.output_sep',
207 208 so2 = 'IPythonWidget.output_sep2',
208 209 ))
209 210
210 211 #-----------------------------------------------------------------------------
211 212 # IPythonQtConsole
212 213 #-----------------------------------------------------------------------------
213 214
214 215 class IPythonQtConsoleApp(BaseIPythonApplication):
215 216 name = 'ipython-qtconsole'
216 217 default_config_file_name='ipython_config.py'
217 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir]
218 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
218 219 flags = Dict(flags)
219 220 aliases = Dict(aliases)
220 221
221 222 kernel_argv = List(Unicode)
222 223
223 224 # connection info:
224 225 ip = Unicode(LOCALHOST, config=True,
225 226 help="""Set the kernel\'s IP address [default localhost].
226 227 If the IP address is something other than localhost, then
227 228 Consoles on other machines will be able to connect
228 229 to the Kernel, so be careful!"""
229 230 )
230 231 hb_port = Int(0, config=True,
231 232 help="set the heartbeat port [default: random]")
232 233 shell_port = Int(0, config=True,
233 234 help="set the shell (XREP) port [default: random]")
234 235 iopub_port = Int(0, config=True,
235 236 help="set the iopub (PUB) port [default: random]")
236 237 stdin_port = Int(0, config=True,
237 238 help="set the stdin (XREQ) port [default: random]")
238 239
239 240 existing = CBool(False, config=True,
240 241 help="Whether to connect to an already running Kernel.")
241 242
242 243 stylesheet = Unicode('', config=True,
243 244 help="path to a custom CSS stylesheet")
244 245
245 246 pure = CBool(False, config=True,
246 247 help="Use a pure Python kernel instead of an IPython kernel.")
247 248 plain = CBool(False, config=True,
248 249 help="Use a plaintext widget instead of rich text (plain can't print/save).")
249 250
250 251 def _pure_changed(self, name, old, new):
251 252 kind = 'plain' if self.plain else 'rich'
252 253 self.config.ConsoleWidget.kind = kind
253 254 if self.pure:
254 255 self.widget_factory = FrontendWidget
255 256 elif self.plain:
256 257 self.widget_factory = IPythonWidget
257 258 else:
258 259 self.widget_factory = RichIPythonWidget
259 260
260 261 _plain_changed = _pure_changed
261 262
262 263 confirm_exit = CBool(True, config=True,
263 264 help="""
264 265 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
265 266 to force a direct exit without any confirmation.""",
266 267 )
267 268
268 269 # the factory for creating a widget
269 270 widget_factory = Any(RichIPythonWidget)
270 271
271 272 def parse_command_line(self, argv=None):
272 273 super(IPythonQtConsoleApp, self).parse_command_line(argv)
273 274 if argv is None:
274 275 argv = sys.argv[1:]
275 276
276 277 self.kernel_argv = list(argv) # copy
277 278
278 279 # scrub frontend-specific flags
279 280 for a in argv:
280 281 if a.startswith('--') and a[2:] in qt_flags:
281 282 self.kernel_argv.remove(a)
282 283
283 284 def init_kernel_manager(self):
284 285 # Don't let Qt or ZMQ swallow KeyboardInterupts.
285 286 signal.signal(signal.SIGINT, signal.SIG_DFL)
286 287
287 288 # Create a KernelManager and start a kernel.
288 289 self.kernel_manager = QtKernelManager(
289 290 shell_address=(self.ip, self.shell_port),
290 291 sub_address=(self.ip, self.iopub_port),
291 292 stdin_address=(self.ip, self.stdin_port),
292 hb_address=(self.ip, self.hb_port)
293 hb_address=(self.ip, self.hb_port),
294 config=self.config
293 295 )
294 296 # start the kernel
295 297 if not self.existing:
296 298 kwargs = dict(ip=self.ip, ipython=not self.pure)
297 299 kwargs['extra_arguments'] = self.kernel_argv
298 300 self.kernel_manager.start_kernel(**kwargs)
299 301 self.kernel_manager.start_channels()
300 302
301 303
302 304 def init_qt_elements(self):
303 305 # Create the widget.
304 306 self.app = QtGui.QApplication([])
305 307 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
306 308 self.widget = self.widget_factory(config=self.config,
307 309 local_kernel=local_kernel)
308 310 self.widget.kernel_manager = self.kernel_manager
309 311 self.window = MainWindow(self.app, self.widget, self.existing,
310 312 may_close=local_kernel,
311 313 confirm_exit=self.confirm_exit)
312 314 self.window.setWindowTitle('Python' if self.pure else 'IPython')
313 315
314 316 def init_colors(self):
315 317 """Configure the coloring of the widget"""
316 318 # Note: This will be dramatically simplified when colors
317 319 # are removed from the backend.
318 320
319 321 if self.pure:
320 322 # only IPythonWidget supports styling
321 323 return
322 324
323 325 # parse the colors arg down to current known labels
324 326 try:
325 327 colors = self.config.ZMQInteractiveShell.colors
326 328 except AttributeError:
327 329 colors = None
328 330 try:
329 331 style = self.config.IPythonWidget.colors
330 332 except AttributeError:
331 333 style = None
332 334
333 335 # find the value for colors:
334 336 if colors:
335 337 colors=colors.lower()
336 338 if colors in ('lightbg', 'light'):
337 339 colors='lightbg'
338 340 elif colors in ('dark', 'linux'):
339 341 colors='linux'
340 342 else:
341 343 colors='nocolor'
342 344 elif style:
343 345 if style=='bw':
344 346 colors='nocolor'
345 347 elif styles.dark_style(style):
346 348 colors='linux'
347 349 else:
348 350 colors='lightbg'
349 351 else:
350 352 colors=None
351 353
352 354 # Configure the style.
353 355 widget = self.widget
354 356 if style:
355 357 widget.style_sheet = styles.sheet_from_template(style, colors)
356 358 widget.syntax_style = style
357 359 widget._syntax_style_changed()
358 360 widget._style_sheet_changed()
359 361 elif colors:
360 362 # use a default style
361 363 widget.set_default_style(colors=colors)
362 364 else:
363 365 # this is redundant for now, but allows the widget's
364 366 # defaults to change
365 367 widget.set_default_style()
366 368
367 369 if self.stylesheet:
368 370 # we got an expicit stylesheet
369 371 if os.path.isfile(self.stylesheet):
370 372 with open(self.stylesheet) as f:
371 373 sheet = f.read()
372 374 widget.style_sheet = sheet
373 375 widget._style_sheet_changed()
374 376 else:
375 377 raise IOError("Stylesheet %r not found."%self.stylesheet)
376 378
377 379 def initialize(self, argv=None):
378 380 super(IPythonQtConsoleApp, self).initialize(argv)
379 381 self.init_kernel_manager()
380 382 self.init_qt_elements()
381 383 self.init_colors()
382 384
383 385 def start(self):
384 386
385 387 # draw the window
386 388 self.window.show()
387 389
388 390 # Start the application main loop.
389 391 self.app.exec_()
390 392
391 393 #-----------------------------------------------------------------------------
392 394 # Main entry point
393 395 #-----------------------------------------------------------------------------
394 396
395 397 def main():
396 398 app = IPythonQtConsoleApp()
397 399 app.initialize()
398 400 app.start()
399 401
400 402
401 403 if __name__ == '__main__':
402 404 main()
@@ -1,673 +1,673 b''
1 1 #!/usr/bin/env python
2 2 """A simple interactive kernel that talks to a frontend over 0MQ.
3 3
4 4 Things to do:
5 5
6 6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 7 call set_parent on all the PUB objects with the message about to be executed.
8 8 * Implement random port and security key logic.
9 9 * Implement control messages.
10 10 * Implement event loop and poll version.
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Standard library imports.
19 19 import __builtin__
20 20 import atexit
21 21 import sys
22 22 import time
23 23 import traceback
24 24 import logging
25 25 # System library imports.
26 26 import zmq
27 27
28 28 # Local imports.
29 29 from IPython.config.configurable import Configurable
30 30 from IPython.config.application import boolean_flag
31 31 from IPython.core.newapplication import ProfileDir
32 32 from IPython.core.shellapp import (
33 33 InteractiveShellApp, shell_flags, shell_aliases
34 34 )
35 35 from IPython.utils import io
36 36 from IPython.utils.jsonutil import json_clean
37 37 from IPython.lib import pylabtools
38 38 from IPython.utils.traitlets import (
39 39 List, Instance, Float, Dict, Bool, Int, Unicode, CaselessStrEnum
40 40 )
41 41
42 42 from entry_point import base_launch_kernel
43 43 from kernelapp import KernelApp, kernel_flags, kernel_aliases
44 44 from iostream import OutStream
45 45 from session import Session, Message
46 46 from zmqshell import ZMQInteractiveShell
47 47
48 48
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Main kernel class
52 52 #-----------------------------------------------------------------------------
53 53
54 54 class Kernel(Configurable):
55 55
56 56 #---------------------------------------------------------------------------
57 57 # Kernel interface
58 58 #---------------------------------------------------------------------------
59 59
60 60 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
61 61 session = Instance(Session)
62 62 shell_socket = Instance('zmq.Socket')
63 63 iopub_socket = Instance('zmq.Socket')
64 64 stdin_socket = Instance('zmq.Socket')
65 65 log = Instance(logging.Logger)
66 66
67 67 # Private interface
68 68
69 69 # Time to sleep after flushing the stdout/err buffers in each execute
70 70 # cycle. While this introduces a hard limit on the minimal latency of the
71 71 # execute cycle, it helps prevent output synchronization problems for
72 72 # clients.
73 73 # Units are in seconds. The minimum zmq latency on local host is probably
74 74 # ~150 microseconds, set this to 500us for now. We may need to increase it
75 75 # a little if it's not enough after more interactive testing.
76 76 _execute_sleep = Float(0.0005, config=True)
77 77
78 78 # Frequency of the kernel's event loop.
79 79 # Units are in seconds, kernel subclasses for GUI toolkits may need to
80 80 # adapt to milliseconds.
81 81 _poll_interval = Float(0.05, config=True)
82 82
83 83 # If the shutdown was requested over the network, we leave here the
84 84 # necessary reply message so it can be sent by our registered atexit
85 85 # handler. This ensures that the reply is only sent to clients truly at
86 86 # the end of our shutdown process (which happens after the underlying
87 87 # IPython shell's own shutdown).
88 88 _shutdown_message = None
89 89
90 90 # This is a dict of port number that the kernel is listening on. It is set
91 91 # by record_ports and used by connect_request.
92 92 _recorded_ports = Dict()
93 93
94 94
95 95
96 96 def __init__(self, **kwargs):
97 97 super(Kernel, self).__init__(**kwargs)
98 98
99 99 # Before we even start up the shell, register *first* our exit handlers
100 100 # so they come before the shell's
101 101 atexit.register(self._at_shutdown)
102 102
103 103 # Initialize the InteractiveShell subclass
104 104 self.shell = ZMQInteractiveShell.instance(config=self.config)
105 105 self.shell.displayhook.session = self.session
106 106 self.shell.displayhook.pub_socket = self.iopub_socket
107 107 self.shell.display_pub.session = self.session
108 108 self.shell.display_pub.pub_socket = self.iopub_socket
109 109
110 110 # TMP - hack while developing
111 111 self.shell._reply_content = None
112 112
113 113 # Build dict of handlers for message types
114 114 msg_types = [ 'execute_request', 'complete_request',
115 115 'object_info_request', 'history_request',
116 116 'connect_request', 'shutdown_request']
117 117 self.handlers = {}
118 118 for msg_type in msg_types:
119 119 self.handlers[msg_type] = getattr(self, msg_type)
120 120
121 121 def do_one_iteration(self):
122 122 """Do one iteration of the kernel's evaluation loop.
123 123 """
124 124 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
125 125 if msg is None:
126 126 return
127 127
128 128 # This assert will raise in versions of zeromq 2.0.7 and lesser.
129 129 # We now require 2.0.8 or above, so we can uncomment for safety.
130 130 # print(ident,msg, file=sys.__stdout__)
131 131 assert ident is not None, "Missing message part."
132 132
133 133 # Print some info about this message and leave a '--->' marker, so it's
134 134 # easier to trace visually the message chain when debugging. Each
135 135 # handler prints its message at the end.
136 136 self.log.debug('\n*** MESSAGE TYPE:'+str(msg['msg_type'])+'***')
137 137 self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ')
138 138
139 139 # Find and call actual handler for message
140 140 handler = self.handlers.get(msg['msg_type'], None)
141 141 if handler is None:
142 142 self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg))
143 143 else:
144 144 handler(ident, msg)
145 145
146 146 # Check whether we should exit, in case the incoming message set the
147 147 # exit flag on
148 148 if self.shell.exit_now:
149 149 self.log.debug('\nExiting IPython kernel...')
150 150 # We do a normal, clean exit, which allows any actions registered
151 151 # via atexit (such as history saving) to take place.
152 152 sys.exit(0)
153 153
154 154
155 155 def start(self):
156 156 """ Start the kernel main loop.
157 157 """
158 158 poller = zmq.Poller()
159 159 poller.register(self.shell_socket, zmq.POLLIN)
160 160 while True:
161 161 try:
162 162 # scale by extra factor of 10, because there is no
163 163 # reason for this to be anything less than ~ 0.1s
164 164 # since it is a real poller and will respond
165 165 # to events immediately
166 166 poller.poll(10*1000*self._poll_interval)
167 167 self.do_one_iteration()
168 168 except KeyboardInterrupt:
169 169 # Ctrl-C shouldn't crash the kernel
170 170 io.raw_print("KeyboardInterrupt caught in kernel")
171 171
172 172 def record_ports(self, ports):
173 173 """Record the ports that this kernel is using.
174 174
175 175 The creator of the Kernel instance must call this methods if they
176 176 want the :meth:`connect_request` method to return the port numbers.
177 177 """
178 178 self._recorded_ports = ports
179 179
180 180 #---------------------------------------------------------------------------
181 181 # Kernel request handlers
182 182 #---------------------------------------------------------------------------
183 183
184 184 def _publish_pyin(self, code, parent):
185 185 """Publish the code request on the pyin stream."""
186 186
187 187 pyin_msg = self.session.send(self.iopub_socket, u'pyin',{u'code':code}, parent=parent)
188 188
189 189 def execute_request(self, ident, parent):
190 190
191 191 status_msg = self.session.send(self.iopub_socket,
192 192 u'status',
193 193 {u'execution_state':u'busy'},
194 194 parent=parent
195 195 )
196 196
197 197 try:
198 198 content = parent[u'content']
199 199 code = content[u'code']
200 200 silent = content[u'silent']
201 201 except:
202 202 self.log.error("Got bad msg: ")
203 203 self.log.error(str(Message(parent)))
204 204 return
205 205
206 206 shell = self.shell # we'll need this a lot here
207 207
208 208 # Replace raw_input. Note that is not sufficient to replace
209 209 # raw_input in the user namespace.
210 210 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
211 211 __builtin__.raw_input = raw_input
212 212
213 213 # Set the parent message of the display hook and out streams.
214 214 shell.displayhook.set_parent(parent)
215 215 shell.display_pub.set_parent(parent)
216 216 sys.stdout.set_parent(parent)
217 217 sys.stderr.set_parent(parent)
218 218
219 219 # Re-broadcast our input for the benefit of listening clients, and
220 220 # start computing output
221 221 if not silent:
222 222 self._publish_pyin(code, parent)
223 223
224 224 reply_content = {}
225 225 try:
226 226 if silent:
227 227 # run_code uses 'exec' mode, so no displayhook will fire, and it
228 228 # doesn't call logging or history manipulations. Print
229 229 # statements in that code will obviously still execute.
230 230 shell.run_code(code)
231 231 else:
232 232 # FIXME: the shell calls the exception handler itself.
233 233 shell.run_cell(code)
234 234 except:
235 235 status = u'error'
236 236 # FIXME: this code right now isn't being used yet by default,
237 237 # because the run_cell() call above directly fires off exception
238 238 # reporting. This code, therefore, is only active in the scenario
239 239 # where runlines itself has an unhandled exception. We need to
240 240 # uniformize this, for all exception construction to come from a
241 241 # single location in the codbase.
242 242 etype, evalue, tb = sys.exc_info()
243 243 tb_list = traceback.format_exception(etype, evalue, tb)
244 244 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
245 245 else:
246 246 status = u'ok'
247 247
248 248 reply_content[u'status'] = status
249 249
250 250 # Return the execution counter so clients can display prompts
251 251 reply_content['execution_count'] = shell.execution_count -1
252 252
253 253 # FIXME - fish exception info out of shell, possibly left there by
254 254 # runlines. We'll need to clean up this logic later.
255 255 if shell._reply_content is not None:
256 256 reply_content.update(shell._reply_content)
257 257 # reset after use
258 258 shell._reply_content = None
259 259
260 260 # At this point, we can tell whether the main code execution succeeded
261 261 # or not. If it did, we proceed to evaluate user_variables/expressions
262 262 if reply_content['status'] == 'ok':
263 263 reply_content[u'user_variables'] = \
264 264 shell.user_variables(content[u'user_variables'])
265 265 reply_content[u'user_expressions'] = \
266 266 shell.user_expressions(content[u'user_expressions'])
267 267 else:
268 268 # If there was an error, don't even try to compute variables or
269 269 # expressions
270 270 reply_content[u'user_variables'] = {}
271 271 reply_content[u'user_expressions'] = {}
272 272
273 273 # Payloads should be retrieved regardless of outcome, so we can both
274 274 # recover partial output (that could have been generated early in a
275 275 # block, before an error) and clear the payload system always.
276 276 reply_content[u'payload'] = shell.payload_manager.read_payload()
277 277 # Be agressive about clearing the payload because we don't want
278 278 # it to sit in memory until the next execute_request comes in.
279 279 shell.payload_manager.clear_payload()
280 280
281 281 # Flush output before sending the reply.
282 282 sys.stdout.flush()
283 283 sys.stderr.flush()
284 284 # FIXME: on rare occasions, the flush doesn't seem to make it to the
285 285 # clients... This seems to mitigate the problem, but we definitely need
286 286 # to better understand what's going on.
287 287 if self._execute_sleep:
288 288 time.sleep(self._execute_sleep)
289 289
290 290 # Send the reply.
291 291 reply_msg = self.session.send(self.shell_socket, u'execute_reply',
292 292 reply_content, parent, ident=ident)
293 293 self.log.debug(str(reply_msg))
294 294
295 295 if reply_msg['content']['status'] == u'error':
296 296 self._abort_queue()
297 297
298 298 status_msg = self.session.send(self.iopub_socket,
299 299 u'status',
300 300 {u'execution_state':u'idle'},
301 301 parent=parent
302 302 )
303 303
304 304 def complete_request(self, ident, parent):
305 305 txt, matches = self._complete(parent)
306 306 matches = {'matches' : matches,
307 307 'matched_text' : txt,
308 308 'status' : 'ok'}
309 309 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
310 310 matches, parent, ident)
311 311 self.log.debug(str(completion_msg))
312 312
313 313 def object_info_request(self, ident, parent):
314 314 object_info = self.shell.object_inspect(parent['content']['oname'])
315 315 # Before we send this object over, we scrub it for JSON usage
316 316 oinfo = json_clean(object_info)
317 317 msg = self.session.send(self.shell_socket, 'object_info_reply',
318 318 oinfo, parent, ident)
319 319 self.log.debug(msg)
320 320
321 321 def history_request(self, ident, parent):
322 322 # We need to pull these out, as passing **kwargs doesn't work with
323 323 # unicode keys before Python 2.6.5.
324 324 hist_access_type = parent['content']['hist_access_type']
325 325 raw = parent['content']['raw']
326 326 output = parent['content']['output']
327 327 if hist_access_type == 'tail':
328 328 n = parent['content']['n']
329 329 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
330 330 include_latest=True)
331 331
332 332 elif hist_access_type == 'range':
333 333 session = parent['content']['session']
334 334 start = parent['content']['start']
335 335 stop = parent['content']['stop']
336 336 hist = self.shell.history_manager.get_range(session, start, stop,
337 337 raw=raw, output=output)
338 338
339 339 elif hist_access_type == 'search':
340 340 pattern = parent['content']['pattern']
341 341 hist = self.shell.history_manager.search(pattern, raw=raw, output=output)
342 342
343 343 else:
344 344 hist = []
345 345 content = {'history' : list(hist)}
346 346 msg = self.session.send(self.shell_socket, 'history_reply',
347 347 content, parent, ident)
348 348 self.log.debug(str(msg))
349 349
350 350 def connect_request(self, ident, parent):
351 351 if self._recorded_ports is not None:
352 352 content = self._recorded_ports.copy()
353 353 else:
354 354 content = {}
355 355 msg = self.session.send(self.shell_socket, 'connect_reply',
356 356 content, parent, ident)
357 357 self.log.debug(msg)
358 358
359 359 def shutdown_request(self, ident, parent):
360 360 self.shell.exit_now = True
361 361 self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
362 362 sys.exit(0)
363 363
364 364 #---------------------------------------------------------------------------
365 365 # Protected interface
366 366 #---------------------------------------------------------------------------
367 367
368 368 def _abort_queue(self):
369 369 while True:
370 370 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
371 371 if msg is None:
372 372 break
373 373 else:
374 374 assert ident is not None, \
375 375 "Unexpected missing message part."
376 376
377 377 self.log.debug("Aborting:\n"+str(Message(msg)))
378 378 msg_type = msg['msg_type']
379 379 reply_type = msg_type.split('_')[0] + '_reply'
380 380 reply_msg = self.session.send(self.shell_socket, reply_type,
381 381 {'status' : 'aborted'}, msg, ident=ident)
382 382 self.log.debug(reply_msg)
383 383 # We need to wait a bit for requests to come in. This can probably
384 384 # be set shorter for true asynchronous clients.
385 385 time.sleep(0.1)
386 386
387 387 def _raw_input(self, prompt, ident, parent):
388 388 # Flush output before making the request.
389 389 sys.stderr.flush()
390 390 sys.stdout.flush()
391 391
392 392 # Send the input request.
393 393 content = dict(prompt=prompt)
394 394 msg = self.session.send(self.stdin_socket, u'input_request', content, parent)
395 395
396 396 # Await a response.
397 397 ident, reply = self.session.recv(self.stdin_socket, 0)
398 398 try:
399 399 value = reply['content']['value']
400 400 except:
401 401 self.log.error("Got bad raw_input reply: ")
402 402 self.log.error(str(Message(parent)))
403 403 value = ''
404 404 return value
405 405
406 406 def _complete(self, msg):
407 407 c = msg['content']
408 408 try:
409 409 cpos = int(c['cursor_pos'])
410 410 except:
411 411 # If we don't get something that we can convert to an integer, at
412 412 # least attempt the completion guessing the cursor is at the end of
413 413 # the text, if there's any, and otherwise of the line
414 414 cpos = len(c['text'])
415 415 if cpos==0:
416 416 cpos = len(c['line'])
417 417 return self.shell.complete(c['text'], c['line'], cpos)
418 418
419 419 def _object_info(self, context):
420 420 symbol, leftover = self._symbol_from_context(context)
421 421 if symbol is not None and not leftover:
422 422 doc = getattr(symbol, '__doc__', '')
423 423 else:
424 424 doc = ''
425 425 object_info = dict(docstring = doc)
426 426 return object_info
427 427
428 428 def _symbol_from_context(self, context):
429 429 if not context:
430 430 return None, context
431 431
432 432 base_symbol_string = context[0]
433 433 symbol = self.shell.user_ns.get(base_symbol_string, None)
434 434 if symbol is None:
435 435 symbol = __builtin__.__dict__.get(base_symbol_string, None)
436 436 if symbol is None:
437 437 return None, context
438 438
439 439 context = context[1:]
440 440 for i, name in enumerate(context):
441 441 new_symbol = getattr(symbol, name, None)
442 442 if new_symbol is None:
443 443 return symbol, context[i:]
444 444 else:
445 445 symbol = new_symbol
446 446
447 447 return symbol, []
448 448
449 449 def _at_shutdown(self):
450 450 """Actions taken at shutdown by the kernel, called by python's atexit.
451 451 """
452 452 # io.rprint("Kernel at_shutdown") # dbg
453 453 if self._shutdown_message is not None:
454 454 self.session.send(self.shell_socket, self._shutdown_message)
455 455 self.session.send(self.iopub_socket, self._shutdown_message)
456 456 self.log.debug(str(self._shutdown_message))
457 457 # A very short sleep to give zmq time to flush its message buffers
458 458 # before Python truly shuts down.
459 459 time.sleep(0.01)
460 460
461 461
462 462 class QtKernel(Kernel):
463 463 """A Kernel subclass with Qt support."""
464 464
465 465 def start(self):
466 466 """Start a kernel with QtPy4 event loop integration."""
467 467
468 468 from PyQt4 import QtCore
469 469 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
470 470
471 471 self.app = get_app_qt4([" "])
472 472 self.app.setQuitOnLastWindowClosed(False)
473 473 self.timer = QtCore.QTimer()
474 474 self.timer.timeout.connect(self.do_one_iteration)
475 475 # Units for the timer are in milliseconds
476 476 self.timer.start(1000*self._poll_interval)
477 477 start_event_loop_qt4(self.app)
478 478
479 479
480 480 class WxKernel(Kernel):
481 481 """A Kernel subclass with Wx support."""
482 482
483 483 def start(self):
484 484 """Start a kernel with wx event loop support."""
485 485
486 486 import wx
487 487 from IPython.lib.guisupport import start_event_loop_wx
488 488
489 489 doi = self.do_one_iteration
490 490 # Wx uses milliseconds
491 491 poll_interval = int(1000*self._poll_interval)
492 492
493 493 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
494 494 # We make the Frame hidden when we create it in the main app below.
495 495 class TimerFrame(wx.Frame):
496 496 def __init__(self, func):
497 497 wx.Frame.__init__(self, None, -1)
498 498 self.timer = wx.Timer(self)
499 499 # Units for the timer are in milliseconds
500 500 self.timer.Start(poll_interval)
501 501 self.Bind(wx.EVT_TIMER, self.on_timer)
502 502 self.func = func
503 503
504 504 def on_timer(self, event):
505 505 self.func()
506 506
507 507 # We need a custom wx.App to create our Frame subclass that has the
508 508 # wx.Timer to drive the ZMQ event loop.
509 509 class IPWxApp(wx.App):
510 510 def OnInit(self):
511 511 self.frame = TimerFrame(doi)
512 512 self.frame.Show(False)
513 513 return True
514 514
515 515 # The redirect=False here makes sure that wx doesn't replace
516 516 # sys.stdout/stderr with its own classes.
517 517 self.app = IPWxApp(redirect=False)
518 518 start_event_loop_wx(self.app)
519 519
520 520
521 521 class TkKernel(Kernel):
522 522 """A Kernel subclass with Tk support."""
523 523
524 524 def start(self):
525 525 """Start a Tk enabled event loop."""
526 526
527 527 import Tkinter
528 528 doi = self.do_one_iteration
529 529 # Tk uses milliseconds
530 530 poll_interval = int(1000*self._poll_interval)
531 531 # For Tkinter, we create a Tk object and call its withdraw method.
532 532 class Timer(object):
533 533 def __init__(self, func):
534 534 self.app = Tkinter.Tk()
535 535 self.app.withdraw()
536 536 self.func = func
537 537
538 538 def on_timer(self):
539 539 self.func()
540 540 self.app.after(poll_interval, self.on_timer)
541 541
542 542 def start(self):
543 543 self.on_timer() # Call it once to get things going.
544 544 self.app.mainloop()
545 545
546 546 self.timer = Timer(doi)
547 547 self.timer.start()
548 548
549 549
550 550 class GTKKernel(Kernel):
551 551 """A Kernel subclass with GTK support."""
552 552
553 553 def start(self):
554 554 """Start the kernel, coordinating with the GTK event loop"""
555 555 from .gui.gtkembed import GTKEmbed
556 556
557 557 gtk_kernel = GTKEmbed(self)
558 558 gtk_kernel.start()
559 559
560 560
561 561 #-----------------------------------------------------------------------------
562 562 # Aliases and Flags for the IPKernelApp
563 563 #-----------------------------------------------------------------------------
564 564
565 565 flags = dict(kernel_flags)
566 566 flags.update(shell_flags)
567 567
568 568 addflag = lambda *args: flags.update(boolean_flag(*args))
569 569
570 570 flags['pylab'] = (
571 571 {'IPKernelApp' : {'pylab' : 'auto'}},
572 572 """Pre-load matplotlib and numpy for interactive use with
573 573 the default matplotlib backend."""
574 574 )
575 575
576 576 aliases = dict(kernel_aliases)
577 577 aliases.update(shell_aliases)
578 578
579 579 # it's possible we don't want short aliases for *all* of these:
580 580 aliases.update(dict(
581 581 pylab='IPKernelApp.pylab',
582 582 ))
583 583
584 584 #-----------------------------------------------------------------------------
585 585 # The IPKernelApp class
586 586 #-----------------------------------------------------------------------------
587 587
588 588 class IPKernelApp(KernelApp, InteractiveShellApp):
589 589 name = 'ipkernel'
590 590
591 591 aliases = Dict(aliases)
592 592 flags = Dict(flags)
593 classes = [Kernel, ZMQInteractiveShell, ProfileDir]
593 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
594 594 # configurables
595 595 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
596 596 config=True,
597 597 help="""Pre-load matplotlib and numpy for interactive use,
598 598 selecting a particular matplotlib backend and loop integration.
599 599 """
600 600 )
601 601 def initialize(self, argv=None):
602 602 super(IPKernelApp, self).initialize(argv)
603 603 self.init_shell()
604 604 self.init_extensions()
605 605 self.init_code()
606 606
607 607 def init_kernel(self):
608 608 kernel_factory = Kernel
609 609
610 610 kernel_map = {
611 611 'qt' : QtKernel,
612 612 'qt4': QtKernel,
613 613 'inline': Kernel,
614 614 'osx': TkKernel,
615 615 'wx' : WxKernel,
616 616 'tk' : TkKernel,
617 617 'gtk': GTKKernel,
618 618 }
619 619
620 620 if self.pylab:
621 621 key = None if self.pylab == 'auto' else self.pylab
622 622 gui, backend = pylabtools.find_gui_and_backend(key)
623 623 kernel_factory = kernel_map.get(gui)
624 624 if kernel_factory is None:
625 625 raise ValueError('GUI is not supported: %r' % gui)
626 626 pylabtools.activate_matplotlib(backend)
627 627
628 628 kernel = kernel_factory(config=self.config, session=self.session,
629 629 shell_socket=self.shell_socket,
630 630 iopub_socket=self.iopub_socket,
631 631 stdin_socket=self.stdin_socket,
632 632 log=self.log
633 633 )
634 634 self.kernel = kernel
635 635 kernel.record_ports(self.ports)
636 636
637 637 if self.pylab:
638 638 pylabtools.import_pylab(kernel.shell.user_ns, backend,
639 639 shell=kernel.shell)
640 640
641 641 def init_shell(self):
642 642 self.shell = self.kernel.shell
643 643
644 644
645 645 #-----------------------------------------------------------------------------
646 646 # Kernel main and launch functions
647 647 #-----------------------------------------------------------------------------
648 648
649 649 def launch_kernel(*args, **kwargs):
650 650 """Launches a localhost IPython kernel, binding to the specified ports.
651 651
652 652 This function simply calls entry_point.base_launch_kernel with the right first
653 653 command to start an ipkernel. See base_launch_kernel for arguments.
654 654
655 655 Returns
656 656 -------
657 657 A tuple of form:
658 658 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
659 659 where kernel_process is a Popen object and the ports are integers.
660 660 """
661 661 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
662 662 *args, **kwargs)
663 663
664 664
665 665 def main():
666 666 """Run an IPKernel as an application"""
667 667 app = IPKernelApp.instance()
668 668 app.initialize()
669 669 app.start()
670 670
671 671
672 672 if __name__ == '__main__':
673 673 main()
@@ -1,214 +1,214 b''
1 1 #!/usr/bin/env python
2 2 """An Application for launching a kernel
3 3
4 4 Authors
5 5 -------
6 6 * MinRK
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 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.txt, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # Standard library imports.
20 20 import os
21 21 import sys
22 22
23 23 # System library imports.
24 24 import zmq
25 25
26 26 # IPython imports.
27 27 from IPython.core.ultratb import FormattedTB
28 28 from IPython.core.newapplication import (
29 29 BaseIPythonApplication, base_flags, base_aliases
30 30 )
31 31 from IPython.utils import io
32 32 from IPython.utils.localinterfaces import LOCALHOST
33 33 from IPython.utils.traitlets import Any, Instance, Dict, Unicode, Int, Bool
34 34 from IPython.utils.importstring import import_item
35 35 # local imports
36 36 from IPython.zmq.heartbeat import Heartbeat
37 37 from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
38 38 from IPython.zmq.session import Session
39 39
40 40
41 41 #-----------------------------------------------------------------------------
42 42 # Flags and Aliases
43 43 #-----------------------------------------------------------------------------
44 44
45 45 kernel_aliases = dict(base_aliases)
46 46 kernel_aliases.update({
47 47 'ip' : 'KernelApp.ip',
48 48 'hb' : 'KernelApp.hb_port',
49 49 'shell' : 'KernelApp.shell_port',
50 50 'iopub' : 'KernelApp.iopub_port',
51 51 'stdin' : 'KernelApp.stdin_port',
52 52 'parent': 'KernelApp.parent',
53 53 })
54 54 if sys.platform.startswith('win'):
55 55 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
56 56
57 57 kernel_flags = dict(base_flags)
58 58 kernel_flags.update({
59 59 'no-stdout' : (
60 60 {'KernelApp' : {'no_stdout' : True}},
61 61 "redirect stdout to the null device"),
62 62 'no-stderr' : (
63 63 {'KernelApp' : {'no_stderr' : True}},
64 64 "redirect stderr to the null device"),
65 65 })
66 66
67 67
68 68 #-----------------------------------------------------------------------------
69 69 # Application class for starting a Kernel
70 70 #-----------------------------------------------------------------------------
71 71
72 72 class KernelApp(BaseIPythonApplication):
73 73 name='pykernel'
74 74 aliases = Dict(kernel_aliases)
75 75 flags = Dict(kernel_flags)
76
76 classes = [Session]
77 77 # the kernel class, as an importstring
78 78 kernel_class = Unicode('IPython.zmq.pykernel.Kernel')
79 79 kernel = Any()
80 80 poller = Any() # don't restrict this even though current pollers are all Threads
81 81 heartbeat = Instance(Heartbeat)
82 82 session = Instance('IPython.zmq.session.Session')
83 83 ports = Dict()
84 84
85 85 # connection info:
86 86 ip = Unicode(LOCALHOST, config=True,
87 87 help="Set the IP or interface on which the kernel will listen.")
88 88 hb_port = Int(0, config=True, help="set the heartbeat port [default: random]")
89 89 shell_port = Int(0, config=True, help="set the shell (XREP) port [default: random]")
90 90 iopub_port = Int(0, config=True, help="set the iopub (PUB) port [default: random]")
91 91 stdin_port = Int(0, config=True, help="set the stdin (XREQ) port [default: random]")
92 92
93 93 # streams, etc.
94 94 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
95 95 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
96 96 outstream_class = Unicode('IPython.zmq.iostream.OutStream', config=True,
97 97 help="The importstring for the OutStream factory")
98 98 displayhook_class = Unicode('IPython.zmq.displayhook.DisplayHook', config=True,
99 99 help="The importstring for the DisplayHook factory")
100 100
101 101 # polling
102 102 parent = Int(0, config=True,
103 103 help="""kill this process if its parent dies. On Windows, the argument
104 104 specifies the HANDLE of the parent process, otherwise it is simply boolean.
105 105 """)
106 106 interrupt = Int(0, config=True,
107 107 help="""ONLY USED ON WINDOWS
108 108 Interrupt this process when the parent is signalled.
109 109 """)
110 110
111 111 def init_crash_handler(self):
112 112 # Install minimal exception handling
113 113 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
114 114 ostream=sys.__stdout__)
115 115
116 116 def init_poller(self):
117 117 if sys.platform == 'win32':
118 118 if self.interrupt or self.parent:
119 119 self.poller = ParentPollerWindows(self.interrupt, self.parent)
120 120 elif self.parent:
121 121 self.poller = ParentPollerUnix()
122 122
123 123 def _bind_socket(self, s, port):
124 124 iface = 'tcp://%s' % self.ip
125 125 if port <= 0:
126 126 port = s.bind_to_random_port(iface)
127 127 else:
128 128 s.bind(iface + ':%i'%port)
129 129 return port
130 130
131 131 def init_sockets(self):
132 132 # Create a context, a session, and the kernel sockets.
133 133 io.raw_print("Starting the kernel at pid:", os.getpid())
134 134 context = zmq.Context.instance()
135 135 # Uncomment this to try closing the context.
136 136 # atexit.register(context.term)
137 137
138 138 self.shell_socket = context.socket(zmq.XREP)
139 139 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
140 140 self.log.debug("shell XREP Channel on port: %i"%self.shell_port)
141 141
142 142 self.iopub_socket = context.socket(zmq.PUB)
143 143 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
144 144 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
145 145
146 146 self.stdin_socket = context.socket(zmq.XREQ)
147 147 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
148 148 self.log.debug("stdin XREQ Channel on port: %i"%self.stdin_port)
149 149
150 150 self.heartbeat = Heartbeat(context, (self.ip, self.hb_port))
151 151 self.hb_port = self.heartbeat.port
152 152 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
153 153
154 154 # Helper to make it easier to connect to an existing kernel, until we have
155 155 # single-port connection negotiation fully implemented.
156 156 self.log.info("To connect another client to this kernel, use:")
157 157 self.log.info("--external shell={0} iopub={1} stdin={2} hb={3}".format(
158 158 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port))
159 159
160 160
161 161 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
162 162 stdin=self.stdin_port, hb=self.hb_port)
163 163
164 164 def init_session(self):
165 165 """create our session object"""
166 self.session = Session(username=u'kernel')
166 self.session = Session(config=self.config, username=u'kernel')
167 167
168 168 def init_io(self):
169 169 """redirects stdout/stderr, and installs a display hook"""
170 170 # Re-direct stdout/stderr, if necessary.
171 171 if self.no_stdout or self.no_stderr:
172 172 blackhole = file(os.devnull, 'w')
173 173 if self.no_stdout:
174 174 sys.stdout = sys.__stdout__ = blackhole
175 175 if self.no_stderr:
176 176 sys.stderr = sys.__stderr__ = blackhole
177 177
178 178 # Redirect input streams and set a display hook.
179 179
180 180 if self.outstream_class:
181 181 outstream_factory = import_item(str(self.outstream_class))
182 182 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
183 183 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
184 184 if self.displayhook_class:
185 185 displayhook_factory = import_item(str(self.displayhook_class))
186 186 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
187 187
188 188 def init_kernel(self):
189 189 """Create the Kernel object itself"""
190 190 kernel_factory = import_item(str(self.kernel_class))
191 191 self.kernel = kernel_factory(config=self.config, session=self.session,
192 192 shell_socket=self.shell_socket,
193 193 iopub_socket=self.iopub_socket,
194 194 stdin_socket=self.stdin_socket,
195 195 log=self.log
196 196 )
197 197 self.kernel.record_ports(self.ports)
198 198
199 199 def initialize(self, argv=None):
200 200 super(KernelApp, self).initialize(argv)
201 201 self.init_session()
202 202 self.init_poller()
203 203 self.init_sockets()
204 204 self.init_io()
205 205 self.init_kernel()
206 206
207 207 def start(self):
208 208 self.heartbeat.start()
209 209 if self.poller is not None:
210 210 self.poller.start()
211 211 try:
212 212 self.kernel.start()
213 213 except KeyboardInterrupt:
214 214 pass
@@ -1,968 +1,976 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 atexit
20 20 import errno
21 21 from Queue import Queue, Empty
22 22 from subprocess import Popen
23 23 import signal
24 24 import sys
25 25 from threading import Thread
26 26 import time
27 27 import logging
28 28
29 29 # System library imports.
30 30 import zmq
31 31 from zmq import POLLIN, POLLOUT, POLLERR
32 32 from zmq.eventloop import ioloop
33 33
34 34 # Local imports.
35 from IPython.config.loader import Config
35 36 from IPython.utils import io
36 37 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
37 38 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
38 39 from session import Session, Message
39 40
40 41 #-----------------------------------------------------------------------------
41 42 # Constants and exceptions
42 43 #-----------------------------------------------------------------------------
43 44
44 45 class InvalidPortNumber(Exception):
45 46 pass
46 47
47 48 #-----------------------------------------------------------------------------
48 49 # Utility functions
49 50 #-----------------------------------------------------------------------------
50 51
51 52 # some utilities to validate message structure, these might get moved elsewhere
52 53 # if they prove to have more generic utility
53 54
54 55 def validate_string_list(lst):
55 56 """Validate that the input is a list of strings.
56 57
57 58 Raises ValueError if not."""
58 59 if not isinstance(lst, list):
59 60 raise ValueError('input %r must be a list' % lst)
60 61 for x in lst:
61 62 if not isinstance(x, basestring):
62 63 raise ValueError('element %r in list must be a string' % x)
63 64
64 65
65 66 def validate_string_dict(dct):
66 67 """Validate that the input is a dict with string keys and values.
67 68
68 69 Raises ValueError if not."""
69 70 for k,v in dct.iteritems():
70 71 if not isinstance(k, basestring):
71 72 raise ValueError('key %r in dict must be a string' % k)
72 73 if not isinstance(v, basestring):
73 74 raise ValueError('value %r in dict must be a string' % v)
74 75
75 76
76 77 #-----------------------------------------------------------------------------
77 78 # ZMQ Socket Channel classes
78 79 #-----------------------------------------------------------------------------
79 80
80 81 class ZMQSocketChannel(Thread):
81 82 """The base class for the channels that use ZMQ sockets.
82 83 """
83 84 context = None
84 85 session = None
85 86 socket = None
86 87 ioloop = None
87 88 iostate = None
88 89 _address = None
89 90
90 91 def __init__(self, context, session, address):
91 92 """Create a channel
92 93
93 94 Parameters
94 95 ----------
95 96 context : :class:`zmq.Context`
96 97 The ZMQ context to use.
97 98 session : :class:`session.Session`
98 99 The session to use.
99 100 address : tuple
100 101 Standard (ip, port) tuple that the kernel is listening on.
101 102 """
102 103 super(ZMQSocketChannel, self).__init__()
103 104 self.daemon = True
104 105
105 106 self.context = context
106 107 self.session = session
107 108 if address[1] == 0:
108 109 message = 'The port number for a channel cannot be 0.'
109 110 raise InvalidPortNumber(message)
110 111 self._address = address
111 112
112 113 def _run_loop(self):
113 114 """Run my loop, ignoring EINTR events in the poller"""
114 115 while True:
115 116 try:
116 117 self.ioloop.start()
117 118 except zmq.ZMQError as e:
118 119 if e.errno == errno.EINTR:
119 120 continue
120 121 else:
121 122 raise
122 123 else:
123 124 break
124 125
125 126 def stop(self):
126 127 """Stop the channel's activity.
127 128
128 129 This calls :method:`Thread.join` and returns when the thread
129 130 terminates. :class:`RuntimeError` will be raised if
130 131 :method:`self.start` is called again.
131 132 """
132 133 self.join()
133 134
134 135 @property
135 136 def address(self):
136 137 """Get the channel's address as an (ip, port) tuple.
137 138
138 139 By the default, the address is (localhost, 0), where 0 means a random
139 140 port.
140 141 """
141 142 return self._address
142 143
143 144 def add_io_state(self, state):
144 145 """Add IO state to the eventloop.
145 146
146 147 Parameters
147 148 ----------
148 149 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
149 150 The IO state flag to set.
150 151
151 152 This is thread safe as it uses the thread safe IOLoop.add_callback.
152 153 """
153 154 def add_io_state_callback():
154 155 if not self.iostate & state:
155 156 self.iostate = self.iostate | state
156 157 self.ioloop.update_handler(self.socket, self.iostate)
157 158 self.ioloop.add_callback(add_io_state_callback)
158 159
159 160 def drop_io_state(self, state):
160 161 """Drop IO state from the eventloop.
161 162
162 163 Parameters
163 164 ----------
164 165 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
165 166 The IO state flag to set.
166 167
167 168 This is thread safe as it uses the thread safe IOLoop.add_callback.
168 169 """
169 170 def drop_io_state_callback():
170 171 if self.iostate & state:
171 172 self.iostate = self.iostate & (~state)
172 173 self.ioloop.update_handler(self.socket, self.iostate)
173 174 self.ioloop.add_callback(drop_io_state_callback)
174 175
175 176
176 177 class ShellSocketChannel(ZMQSocketChannel):
177 178 """The XREQ channel for issues request/replies to the kernel.
178 179 """
179 180
180 181 command_queue = None
181 182
182 183 def __init__(self, context, session, address):
183 184 super(ShellSocketChannel, self).__init__(context, session, address)
184 185 self.command_queue = Queue()
185 186 self.ioloop = ioloop.IOLoop()
186 187
187 188 def run(self):
188 189 """The thread's main activity. Call start() instead."""
189 190 self.socket = self.context.socket(zmq.XREQ)
190 191 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
191 192 self.socket.connect('tcp://%s:%i' % self.address)
192 193 self.iostate = POLLERR|POLLIN
193 194 self.ioloop.add_handler(self.socket, self._handle_events,
194 195 self.iostate)
195 196 self._run_loop()
196 197
197 198 def stop(self):
198 199 self.ioloop.stop()
199 200 super(ShellSocketChannel, self).stop()
200 201
201 202 def call_handlers(self, msg):
202 203 """This method is called in the ioloop thread when a message arrives.
203 204
204 205 Subclasses should override this method to handle incoming messages.
205 206 It is important to remember that this method is called in the thread
206 207 so that some logic must be done to ensure that the application leve
207 208 handlers are called in the application thread.
208 209 """
209 210 raise NotImplementedError('call_handlers must be defined in a subclass.')
210 211
211 212 def execute(self, code, silent=False,
212 213 user_variables=None, user_expressions=None):
213 214 """Execute code in the kernel.
214 215
215 216 Parameters
216 217 ----------
217 218 code : str
218 219 A string of Python code.
219 220
220 221 silent : bool, optional (default False)
221 222 If set, the kernel will execute the code as quietly possible.
222 223
223 224 user_variables : list, optional
224 225 A list of variable names to pull from the user's namespace. They
225 226 will come back as a dict with these names as keys and their
226 227 :func:`repr` as values.
227 228
228 229 user_expressions : dict, optional
229 230 A dict with string keys and to pull from the user's
230 231 namespace. They will come back as a dict with these names as keys
231 232 and their :func:`repr` as values.
232 233
233 234 Returns
234 235 -------
235 236 The msg_id of the message sent.
236 237 """
237 238 if user_variables is None:
238 239 user_variables = []
239 240 if user_expressions is None:
240 241 user_expressions = {}
241 242
242 243 # Don't waste network traffic if inputs are invalid
243 244 if not isinstance(code, basestring):
244 245 raise ValueError('code %r must be a string' % code)
245 246 validate_string_list(user_variables)
246 247 validate_string_dict(user_expressions)
247 248
248 249 # Create class for content/msg creation. Related to, but possibly
249 250 # not in Session.
250 251 content = dict(code=code, silent=silent,
251 252 user_variables=user_variables,
252 253 user_expressions=user_expressions)
253 254 msg = self.session.msg('execute_request', content)
254 255 self._queue_request(msg)
255 256 return msg['header']['msg_id']
256 257
257 258 def complete(self, text, line, cursor_pos, block=None):
258 259 """Tab complete text in the kernel's namespace.
259 260
260 261 Parameters
261 262 ----------
262 263 text : str
263 264 The text to complete.
264 265 line : str
265 266 The full line of text that is the surrounding context for the
266 267 text to complete.
267 268 cursor_pos : int
268 269 The position of the cursor in the line where the completion was
269 270 requested.
270 271 block : str, optional
271 272 The full block of code in which the completion is being requested.
272 273
273 274 Returns
274 275 -------
275 276 The msg_id of the message sent.
276 277 """
277 278 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
278 279 msg = self.session.msg('complete_request', content)
279 280 self._queue_request(msg)
280 281 return msg['header']['msg_id']
281 282
282 283 def object_info(self, oname):
283 284 """Get metadata information about an object.
284 285
285 286 Parameters
286 287 ----------
287 288 oname : str
288 289 A string specifying the object name.
289 290
290 291 Returns
291 292 -------
292 293 The msg_id of the message sent.
293 294 """
294 295 content = dict(oname=oname)
295 296 msg = self.session.msg('object_info_request', content)
296 297 self._queue_request(msg)
297 298 return msg['header']['msg_id']
298 299
299 300 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
300 301 """Get entries from the history list.
301 302
302 303 Parameters
303 304 ----------
304 305 raw : bool
305 306 If True, return the raw input.
306 307 output : bool
307 308 If True, then return the output as well.
308 309 hist_access_type : str
309 310 'range' (fill in session, start and stop params), 'tail' (fill in n)
310 311 or 'search' (fill in pattern param).
311 312
312 313 session : int
313 314 For a range request, the session from which to get lines. Session
314 315 numbers are positive integers; negative ones count back from the
315 316 current session.
316 317 start : int
317 318 The first line number of a history range.
318 319 stop : int
319 320 The final (excluded) line number of a history range.
320 321
321 322 n : int
322 323 The number of lines of history to get for a tail request.
323 324
324 325 pattern : str
325 326 The glob-syntax pattern for a search request.
326 327
327 328 Returns
328 329 -------
329 330 The msg_id of the message sent.
330 331 """
331 332 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
332 333 **kwargs)
333 334 msg = self.session.msg('history_request', content)
334 335 self._queue_request(msg)
335 336 return msg['header']['msg_id']
336 337
337 338 def shutdown(self, restart=False):
338 339 """Request an immediate kernel shutdown.
339 340
340 341 Upon receipt of the (empty) reply, client code can safely assume that
341 342 the kernel has shut down and it's safe to forcefully terminate it if
342 343 it's still alive.
343 344
344 345 The kernel will send the reply via a function registered with Python's
345 346 atexit module, ensuring it's truly done as the kernel is done with all
346 347 normal operation.
347 348 """
348 349 # Send quit message to kernel. Once we implement kernel-side setattr,
349 350 # this should probably be done that way, but for now this will do.
350 351 msg = self.session.msg('shutdown_request', {'restart':restart})
351 352 self._queue_request(msg)
352 353 return msg['header']['msg_id']
353 354
354 355 def _handle_events(self, socket, events):
355 356 if events & POLLERR:
356 357 self._handle_err()
357 358 if events & POLLOUT:
358 359 self._handle_send()
359 360 if events & POLLIN:
360 361 self._handle_recv()
361 362
362 363 def _handle_recv(self):
363 364 ident,msg = self.session.recv(self.socket, 0)
364 365 self.call_handlers(msg)
365 366
366 367 def _handle_send(self):
367 368 try:
368 369 msg = self.command_queue.get(False)
369 370 except Empty:
370 371 pass
371 372 else:
372 373 self.session.send(self.socket,msg)
373 374 if self.command_queue.empty():
374 375 self.drop_io_state(POLLOUT)
375 376
376 377 def _handle_err(self):
377 378 # We don't want to let this go silently, so eventually we should log.
378 379 raise zmq.ZMQError()
379 380
380 381 def _queue_request(self, msg):
381 382 self.command_queue.put(msg)
382 383 self.add_io_state(POLLOUT)
383 384
384 385
385 386 class SubSocketChannel(ZMQSocketChannel):
386 387 """The SUB channel which listens for messages that the kernel publishes.
387 388 """
388 389
389 390 def __init__(self, context, session, address):
390 391 super(SubSocketChannel, self).__init__(context, session, address)
391 392 self.ioloop = ioloop.IOLoop()
392 393
393 394 def run(self):
394 395 """The thread's main activity. Call start() instead."""
395 396 self.socket = self.context.socket(zmq.SUB)
396 397 self.socket.setsockopt(zmq.SUBSCRIBE,'')
397 398 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
398 399 self.socket.connect('tcp://%s:%i' % self.address)
399 400 self.iostate = POLLIN|POLLERR
400 401 self.ioloop.add_handler(self.socket, self._handle_events,
401 402 self.iostate)
402 403 self._run_loop()
403 404
404 405 def stop(self):
405 406 self.ioloop.stop()
406 407 super(SubSocketChannel, self).stop()
407 408
408 409 def call_handlers(self, msg):
409 410 """This method is called in the ioloop thread when a message arrives.
410 411
411 412 Subclasses should override this method to handle incoming messages.
412 413 It is important to remember that this method is called in the thread
413 414 so that some logic must be done to ensure that the application leve
414 415 handlers are called in the application thread.
415 416 """
416 417 raise NotImplementedError('call_handlers must be defined in a subclass.')
417 418
418 419 def flush(self, timeout=1.0):
419 420 """Immediately processes all pending messages on the SUB channel.
420 421
421 422 Callers should use this method to ensure that :method:`call_handlers`
422 423 has been called for all messages that have been received on the
423 424 0MQ SUB socket of this channel.
424 425
425 426 This method is thread safe.
426 427
427 428 Parameters
428 429 ----------
429 430 timeout : float, optional
430 431 The maximum amount of time to spend flushing, in seconds. The
431 432 default is one second.
432 433 """
433 434 # We do the IOLoop callback process twice to ensure that the IOLoop
434 435 # gets to perform at least one full poll.
435 436 stop_time = time.time() + timeout
436 437 for i in xrange(2):
437 438 self._flushed = False
438 439 self.ioloop.add_callback(self._flush)
439 440 while not self._flushed and time.time() < stop_time:
440 441 time.sleep(0.01)
441 442
442 443 def _handle_events(self, socket, events):
443 444 # Turn on and off POLLOUT depending on if we have made a request
444 445 if events & POLLERR:
445 446 self._handle_err()
446 447 if events & POLLIN:
447 448 self._handle_recv()
448 449
449 450 def _handle_err(self):
450 451 # We don't want to let this go silently, so eventually we should log.
451 452 raise zmq.ZMQError()
452 453
453 454 def _handle_recv(self):
454 455 # Get all of the messages we can
455 456 while True:
456 457 try:
457 458 ident,msg = self.session.recv(self.socket)
458 459 except zmq.ZMQError:
459 460 # Check the errno?
460 461 # Will this trigger POLLERR?
461 462 break
462 463 else:
463 464 if msg is None:
464 465 break
465 466 self.call_handlers(msg)
466 467
467 468 def _flush(self):
468 469 """Callback for :method:`self.flush`."""
469 470 self._flushed = True
470 471
471 472
472 473 class StdInSocketChannel(ZMQSocketChannel):
473 474 """A reply channel to handle raw_input requests that the kernel makes."""
474 475
475 476 msg_queue = None
476 477
477 478 def __init__(self, context, session, address):
478 479 super(StdInSocketChannel, self).__init__(context, session, address)
479 480 self.ioloop = ioloop.IOLoop()
480 481 self.msg_queue = Queue()
481 482
482 483 def run(self):
483 484 """The thread's main activity. Call start() instead."""
484 485 self.socket = self.context.socket(zmq.XREQ)
485 486 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
486 487 self.socket.connect('tcp://%s:%i' % self.address)
487 488 self.iostate = POLLERR|POLLIN
488 489 self.ioloop.add_handler(self.socket, self._handle_events,
489 490 self.iostate)
490 491 self._run_loop()
491 492
492 493 def stop(self):
493 494 self.ioloop.stop()
494 495 super(StdInSocketChannel, self).stop()
495 496
496 497 def call_handlers(self, msg):
497 498 """This method is called in the ioloop thread when a message arrives.
498 499
499 500 Subclasses should override this method to handle incoming messages.
500 501 It is important to remember that this method is called in the thread
501 502 so that some logic must be done to ensure that the application leve
502 503 handlers are called in the application thread.
503 504 """
504 505 raise NotImplementedError('call_handlers must be defined in a subclass.')
505 506
506 507 def input(self, string):
507 508 """Send a string of raw input to the kernel."""
508 509 content = dict(value=string)
509 510 msg = self.session.msg('input_reply', content)
510 511 self._queue_reply(msg)
511 512
512 513 def _handle_events(self, socket, events):
513 514 if events & POLLERR:
514 515 self._handle_err()
515 516 if events & POLLOUT:
516 517 self._handle_send()
517 518 if events & POLLIN:
518 519 self._handle_recv()
519 520
520 521 def _handle_recv(self):
521 522 ident,msg = self.session.recv(self.socket, 0)
522 523 self.call_handlers(msg)
523 524
524 525 def _handle_send(self):
525 526 try:
526 527 msg = self.msg_queue.get(False)
527 528 except Empty:
528 529 pass
529 530 else:
530 531 self.session.send(self.socket,msg)
531 532 if self.msg_queue.empty():
532 533 self.drop_io_state(POLLOUT)
533 534
534 535 def _handle_err(self):
535 536 # We don't want to let this go silently, so eventually we should log.
536 537 raise zmq.ZMQError()
537 538
538 539 def _queue_reply(self, msg):
539 540 self.msg_queue.put(msg)
540 541 self.add_io_state(POLLOUT)
541 542
542 543
543 544 class HBSocketChannel(ZMQSocketChannel):
544 545 """The heartbeat channel which monitors the kernel heartbeat.
545 546
546 547 Note that the heartbeat channel is paused by default. As long as you start
547 548 this channel, the kernel manager will ensure that it is paused and un-paused
548 549 as appropriate.
549 550 """
550 551
551 552 time_to_dead = 3.0
552 553 socket = None
553 554 poller = None
554 555 _running = None
555 556 _pause = None
556 557
557 558 def __init__(self, context, session, address):
558 559 super(HBSocketChannel, self).__init__(context, session, address)
559 560 self._running = False
560 561 self._pause = True
561 562
562 563 def _create_socket(self):
563 564 self.socket = self.context.socket(zmq.REQ)
564 565 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
565 566 self.socket.connect('tcp://%s:%i' % self.address)
566 567 self.poller = zmq.Poller()
567 568 self.poller.register(self.socket, zmq.POLLIN)
568 569
569 570 def run(self):
570 571 """The thread's main activity. Call start() instead."""
571 572 self._create_socket()
572 573 self._running = True
573 574 while self._running:
574 575 if self._pause:
575 576 time.sleep(self.time_to_dead)
576 577 else:
577 578 since_last_heartbeat = 0.0
578 579 request_time = time.time()
579 580 try:
580 581 #io.rprint('Ping from HB channel') # dbg
581 582 self.socket.send(b'ping')
582 583 except zmq.ZMQError, e:
583 584 #io.rprint('*** HB Error:', e) # dbg
584 585 if e.errno == zmq.EFSM:
585 586 #io.rprint('sleep...', self.time_to_dead) # dbg
586 587 time.sleep(self.time_to_dead)
587 588 self._create_socket()
588 589 else:
589 590 raise
590 591 else:
591 592 while True:
592 593 try:
593 594 self.socket.recv(zmq.NOBLOCK)
594 595 except zmq.ZMQError, e:
595 596 #io.rprint('*** HB Error 2:', e) # dbg
596 597 if e.errno == zmq.EAGAIN:
597 598 before_poll = time.time()
598 599 until_dead = self.time_to_dead - (before_poll -
599 600 request_time)
600 601
601 602 # When the return value of poll() is an empty
602 603 # list, that is when things have gone wrong
603 604 # (zeromq bug). As long as it is not an empty
604 605 # list, poll is working correctly even if it
605 606 # returns quickly. Note: poll timeout is in
606 607 # milliseconds.
607 608 if until_dead > 0.0:
608 609 while True:
609 610 try:
610 611 self.poller.poll(1000 * until_dead)
611 612 except zmq.ZMQError as e:
612 613 if e.errno == errno.EINTR:
613 614 continue
614 615 else:
615 616 raise
616 617 else:
617 618 break
618 619
619 620 since_last_heartbeat = time.time()-request_time
620 621 if since_last_heartbeat > self.time_to_dead:
621 622 self.call_handlers(since_last_heartbeat)
622 623 break
623 624 else:
624 625 # FIXME: We should probably log this instead.
625 626 raise
626 627 else:
627 628 until_dead = self.time_to_dead - (time.time() -
628 629 request_time)
629 630 if until_dead > 0.0:
630 631 #io.rprint('sleep...', self.time_to_dead) # dbg
631 632 time.sleep(until_dead)
632 633 break
633 634
634 635 def pause(self):
635 636 """Pause the heartbeat."""
636 637 self._pause = True
637 638
638 639 def unpause(self):
639 640 """Unpause the heartbeat."""
640 641 self._pause = False
641 642
642 643 def is_beating(self):
643 644 """Is the heartbeat running and not paused."""
644 645 if self.is_alive() and not self._pause:
645 646 return True
646 647 else:
647 648 return False
648 649
649 650 def stop(self):
650 651 self._running = False
651 652 super(HBSocketChannel, self).stop()
652 653
653 654 def call_handlers(self, since_last_heartbeat):
654 655 """This method is called in the ioloop thread when a message arrives.
655 656
656 657 Subclasses should override this method to handle incoming messages.
657 658 It is important to remember that this method is called in the thread
658 659 so that some logic must be done to ensure that the application leve
659 660 handlers are called in the application thread.
660 661 """
661 662 raise NotImplementedError('call_handlers must be defined in a subclass.')
662 663
663 664
664 665 #-----------------------------------------------------------------------------
665 666 # Main kernel manager class
666 667 #-----------------------------------------------------------------------------
667 668
668 669 class KernelManager(HasTraits):
669 670 """ Manages a kernel for a frontend.
670 671
671 672 The SUB channel is for the frontend to receive messages published by the
672 673 kernel.
673 674
674 675 The REQ channel is for the frontend to make requests of the kernel.
675 676
676 677 The REP channel is for the kernel to request stdin (raw_input) from the
677 678 frontend.
678 679 """
680 # config object for passing to child configurables
681 config = Instance(Config)
682
679 683 # The PyZMQ Context to use for communication with the kernel.
680 context = Instance(zmq.Context,(),{})
684 context = Instance(zmq.Context)
685 def _context_default(self):
686 return zmq.Context.instance()
681 687
682 688 # The Session to use for communication with the kernel.
683 session = Instance(Session,(),{})
689 session = Instance(Session)
684 690
685 691 # The kernel process with which the KernelManager is communicating.
686 692 kernel = Instance(Popen)
687 693
688 694 # The addresses for the communication channels.
689 695 shell_address = TCPAddress((LOCALHOST, 0))
690 696 sub_address = TCPAddress((LOCALHOST, 0))
691 697 stdin_address = TCPAddress((LOCALHOST, 0))
692 698 hb_address = TCPAddress((LOCALHOST, 0))
693 699
694 700 # The classes to use for the various channels.
695 701 shell_channel_class = Type(ShellSocketChannel)
696 702 sub_channel_class = Type(SubSocketChannel)
697 703 stdin_channel_class = Type(StdInSocketChannel)
698 704 hb_channel_class = Type(HBSocketChannel)
699 705
700 706 # Protected traits.
701 707 _launch_args = Any
702 708 _shell_channel = Any
703 709 _sub_channel = Any
704 710 _stdin_channel = Any
705 711 _hb_channel = Any
706 712
707 713 def __init__(self, **kwargs):
708 714 super(KernelManager, self).__init__(**kwargs)
715 if self.session is None:
716 self.session = Session(config=self.config)
709 717 # Uncomment this to try closing the context.
710 # atexit.register(self.context.close)
718 # atexit.register(self.context.term)
711 719
712 720 #--------------------------------------------------------------------------
713 721 # Channel management methods:
714 722 #--------------------------------------------------------------------------
715 723
716 724 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
717 725 """Starts the channels for this kernel.
718 726
719 727 This will create the channels if they do not exist and then start
720 728 them. If port numbers of 0 are being used (random ports) then you
721 729 must first call :method:`start_kernel`. If the channels have been
722 730 stopped and you call this, :class:`RuntimeError` will be raised.
723 731 """
724 732 if shell:
725 733 self.shell_channel.start()
726 734 if sub:
727 735 self.sub_channel.start()
728 736 if stdin:
729 737 self.stdin_channel.start()
730 738 if hb:
731 739 self.hb_channel.start()
732 740
733 741 def stop_channels(self):
734 742 """Stops all the running channels for this kernel.
735 743 """
736 744 if self.shell_channel.is_alive():
737 745 self.shell_channel.stop()
738 746 if self.sub_channel.is_alive():
739 747 self.sub_channel.stop()
740 748 if self.stdin_channel.is_alive():
741 749 self.stdin_channel.stop()
742 750 if self.hb_channel.is_alive():
743 751 self.hb_channel.stop()
744 752
745 753 @property
746 754 def channels_running(self):
747 755 """Are any of the channels created and running?"""
748 756 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
749 757 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
750 758
751 759 #--------------------------------------------------------------------------
752 760 # Kernel process management methods:
753 761 #--------------------------------------------------------------------------
754 762
755 763 def start_kernel(self, **kw):
756 764 """Starts a kernel process and configures the manager to use it.
757 765
758 766 If random ports (port=0) are being used, this method must be called
759 767 before the channels are created.
760 768
761 769 Parameters:
762 770 -----------
763 771 ipython : bool, optional (default True)
764 772 Whether to use an IPython kernel instead of a plain Python kernel.
765 773
766 774 **kw : optional
767 775 See respective options for IPython and Python kernels.
768 776 """
769 777 shell, sub, stdin, hb = self.shell_address, self.sub_address, \
770 778 self.stdin_address, self.hb_address
771 779 if shell[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
772 780 stdin[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
773 781 raise RuntimeError("Can only launch a kernel on a local interface. "
774 782 "Make sure that the '*_address' attributes are "
775 783 "configured properly. "
776 784 "Currently valid addresses are: %s"%LOCAL_IPS
777 785 )
778 786
779 787 self._launch_args = kw.copy()
780 788 if kw.pop('ipython', True):
781 789 from ipkernel import launch_kernel
782 790 else:
783 791 from pykernel import launch_kernel
784 792 self.kernel, xrep, pub, req, _hb = launch_kernel(
785 793 shell_port=shell[1], iopub_port=sub[1],
786 794 stdin_port=stdin[1], hb_port=hb[1], **kw)
787 795 self.shell_address = (shell[0], xrep)
788 796 self.sub_address = (sub[0], pub)
789 797 self.stdin_address = (stdin[0], req)
790 798 self.hb_address = (hb[0], _hb)
791 799
792 800 def shutdown_kernel(self, restart=False):
793 801 """ Attempts to the stop the kernel process cleanly. If the kernel
794 802 cannot be stopped, it is killed, if possible.
795 803 """
796 804 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
797 805 if sys.platform == 'win32':
798 806 self.kill_kernel()
799 807 return
800 808
801 809 # Pause the heart beat channel if it exists.
802 810 if self._hb_channel is not None:
803 811 self._hb_channel.pause()
804 812
805 813 # Don't send any additional kernel kill messages immediately, to give
806 814 # the kernel a chance to properly execute shutdown actions. Wait for at
807 815 # most 1s, checking every 0.1s.
808 816 self.shell_channel.shutdown(restart=restart)
809 817 for i in range(10):
810 818 if self.is_alive:
811 819 time.sleep(0.1)
812 820 else:
813 821 break
814 822 else:
815 823 # OK, we've waited long enough.
816 824 if self.has_kernel:
817 825 self.kill_kernel()
818 826
819 827 def restart_kernel(self, now=False, **kw):
820 828 """Restarts a kernel with the arguments that were used to launch it.
821 829
822 830 If the old kernel was launched with random ports, the same ports will be
823 831 used for the new kernel.
824 832
825 833 Parameters
826 834 ----------
827 835 now : bool, optional
828 836 If True, the kernel is forcefully restarted *immediately*, without
829 837 having a chance to do any cleanup action. Otherwise the kernel is
830 838 given 1s to clean up before a forceful restart is issued.
831 839
832 840 In all cases the kernel is restarted, the only difference is whether
833 841 it is given a chance to perform a clean shutdown or not.
834 842
835 843 **kw : optional
836 844 Any options specified here will replace those used to launch the
837 845 kernel.
838 846 """
839 847 if self._launch_args is None:
840 848 raise RuntimeError("Cannot restart the kernel. "
841 849 "No previous call to 'start_kernel'.")
842 850 else:
843 851 # Stop currently running kernel.
844 852 if self.has_kernel:
845 853 if now:
846 854 self.kill_kernel()
847 855 else:
848 856 self.shutdown_kernel(restart=True)
849 857
850 858 # Start new kernel.
851 859 self._launch_args.update(kw)
852 860 self.start_kernel(**self._launch_args)
853 861
854 862 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
855 863 # unless there is some delay here.
856 864 if sys.platform == 'win32':
857 865 time.sleep(0.2)
858 866
859 867 @property
860 868 def has_kernel(self):
861 869 """Returns whether a kernel process has been specified for the kernel
862 870 manager.
863 871 """
864 872 return self.kernel is not None
865 873
866 874 def kill_kernel(self):
867 875 """ Kill the running kernel. """
868 876 if self.has_kernel:
869 877 # Pause the heart beat channel if it exists.
870 878 if self._hb_channel is not None:
871 879 self._hb_channel.pause()
872 880
873 881 # Attempt to kill the kernel.
874 882 try:
875 883 self.kernel.kill()
876 884 except OSError, e:
877 885 # In Windows, we will get an Access Denied error if the process
878 886 # has already terminated. Ignore it.
879 887 if sys.platform == 'win32':
880 888 if e.winerror != 5:
881 889 raise
882 890 # On Unix, we may get an ESRCH error if the process has already
883 891 # terminated. Ignore it.
884 892 else:
885 893 from errno import ESRCH
886 894 if e.errno != ESRCH:
887 895 raise
888 896 self.kernel = None
889 897 else:
890 898 raise RuntimeError("Cannot kill kernel. No kernel is running!")
891 899
892 900 def interrupt_kernel(self):
893 901 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
894 902 well supported on all platforms.
895 903 """
896 904 if self.has_kernel:
897 905 if sys.platform == 'win32':
898 906 from parentpoller import ParentPollerWindows as Poller
899 907 Poller.send_interrupt(self.kernel.win32_interrupt_event)
900 908 else:
901 909 self.kernel.send_signal(signal.SIGINT)
902 910 else:
903 911 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
904 912
905 913 def signal_kernel(self, signum):
906 914 """ Sends a signal to the kernel. Note that since only SIGTERM is
907 915 supported on Windows, this function is only useful on Unix systems.
908 916 """
909 917 if self.has_kernel:
910 918 self.kernel.send_signal(signum)
911 919 else:
912 920 raise RuntimeError("Cannot signal kernel. No kernel is running!")
913 921
914 922 @property
915 923 def is_alive(self):
916 924 """Is the kernel process still running?"""
917 925 # FIXME: not using a heartbeat means this method is broken for any
918 926 # remote kernel, it's only capable of handling local kernels.
919 927 if self.has_kernel:
920 928 if self.kernel.poll() is None:
921 929 return True
922 930 else:
923 931 return False
924 932 else:
925 933 # We didn't start the kernel with this KernelManager so we don't
926 934 # know if it is running. We should use a heartbeat for this case.
927 935 return True
928 936
929 937 #--------------------------------------------------------------------------
930 938 # Channels used for communication with the kernel:
931 939 #--------------------------------------------------------------------------
932 940
933 941 @property
934 942 def shell_channel(self):
935 943 """Get the REQ socket channel object to make requests of the kernel."""
936 944 if self._shell_channel is None:
937 945 self._shell_channel = self.shell_channel_class(self.context,
938 946 self.session,
939 947 self.shell_address)
940 948 return self._shell_channel
941 949
942 950 @property
943 951 def sub_channel(self):
944 952 """Get the SUB socket channel object."""
945 953 if self._sub_channel is None:
946 954 self._sub_channel = self.sub_channel_class(self.context,
947 955 self.session,
948 956 self.sub_address)
949 957 return self._sub_channel
950 958
951 959 @property
952 960 def stdin_channel(self):
953 961 """Get the REP socket channel object to handle stdin (raw_input)."""
954 962 if self._stdin_channel is None:
955 963 self._stdin_channel = self.stdin_channel_class(self.context,
956 964 self.session,
957 965 self.stdin_address)
958 966 return self._stdin_channel
959 967
960 968 @property
961 969 def hb_channel(self):
962 970 """Get the heartbeat socket channel object to check that the
963 971 kernel is alive."""
964 972 if self._hb_channel is None:
965 973 self._hb_channel = self.hb_channel_class(self.context,
966 974 self.session,
967 975 self.hb_address)
968 976 return self._hb_channel
General Comments 0
You need to be logged in to leave comments. Login now